@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.4

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.
Files changed (258) hide show
  1. package/assets/styles/global/_layout.scss +4 -0
  2. package/assets/translations/en-us.yaml +144 -41
  3. package/assets/translations/zh-hans.yaml +1 -7
  4. package/chart/monitoring/ClusterSelector.vue +0 -21
  5. package/chart/monitoring/prometheus/index.vue +6 -3
  6. package/components/CruResource.vue +161 -14
  7. package/components/ExplorerMembers.vue +8 -4
  8. package/components/ExplorerProjectsNamespaces.vue +10 -6
  9. package/components/GrowlManager.vue +4 -0
  10. package/components/MgmtNodeList.vue +184 -0
  11. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  12. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  13. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  14. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  15. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  16. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  17. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  18. package/components/ResourceDetail/index.vue +1 -1
  19. package/components/ResourceList/Masthead.vue +7 -1
  20. package/components/ResourceList/index.vue +82 -1
  21. package/components/RichTranslation.vue +5 -2
  22. package/components/Setting.vue +1 -0
  23. package/components/SubtleLink.vue +31 -6
  24. package/components/Tabbed/Tab.vue +29 -3
  25. package/components/Tabbed/index.vue +25 -3
  26. package/components/TableOfContents/TableOfContents.vue +109 -0
  27. package/components/TableOfContents/composables.ts +258 -0
  28. package/components/Window/ContainerShell.vue +21 -11
  29. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  30. package/components/Wizard.vue +9 -4
  31. package/components/fleet/AppCoChartGrid.vue +401 -0
  32. package/components/fleet/AppCoEmptyState.vue +127 -0
  33. package/components/fleet/AppCoPageHeader.vue +119 -0
  34. package/components/fleet/AppCoVersionSelect.vue +70 -0
  35. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  36. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  37. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  38. package/components/fleet/FleetIntro.vue +7 -3
  39. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  40. package/components/fleet/FleetSecretSelector.vue +5 -3
  41. package/components/fleet/FleetValuesFrom.vue +8 -2
  42. package/components/fleet/GitRepoTargetTab.vue +0 -2
  43. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  44. package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
  45. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  46. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  47. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  48. package/components/fleet/HelmOpTargetTab.vue +64 -60
  49. package/components/fleet/HelmOpValuesTab.vue +129 -105
  50. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  51. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  52. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  53. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  54. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  55. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  56. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  57. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  58. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  59. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  60. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  61. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  62. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  63. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  64. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  65. package/components/fleet/dashboard/Empty.vue +8 -4
  66. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  67. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  68. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  69. package/components/form/ArrayList.vue +61 -4
  70. package/components/form/KeyValue.vue +23 -2
  71. package/components/form/LabeledSelect.vue +39 -1
  72. package/components/form/Labels.vue +22 -3
  73. package/components/form/NameNsDescription.vue +13 -5
  74. package/components/form/ResourceTabs/index.vue +1 -0
  75. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  76. package/components/formatter/InternalExternalIP.vue +10 -4
  77. package/components/formatter/ServiceTargets.vue +26 -7
  78. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  79. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  80. package/components/nav/Header.vue +4 -0
  81. package/components/nav/TopLevelMenu.vue +7 -2
  82. package/components/nav/__tests__/Header.test.ts +15 -0
  83. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  84. package/components/templates/default.vue +9 -4
  85. package/components/templates/home.vue +9 -4
  86. package/components/templates/plain.vue +9 -4
  87. package/composables/useHelmOpResources.test.ts +56 -0
  88. package/composables/useHelmOpResources.ts +32 -0
  89. package/composables/useStateColor.test.ts +325 -0
  90. package/composables/useStateColor.ts +128 -0
  91. package/config/home-links.js +1 -1
  92. package/config/labels-annotations.js +1 -0
  93. package/config/product/explorer.js +17 -4
  94. package/config/product/manager.js +2 -0
  95. package/config/router/index.js +16 -0
  96. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  97. package/config/router/navigation-guards/authentication.js +10 -4
  98. package/config/router/routes.js +20 -6
  99. package/config/settings.ts +0 -2
  100. package/config/table-headers.js +3 -4
  101. package/config/types.js +9 -0
  102. package/core/plugin-products-base.ts +3 -3
  103. package/core/plugin-types.ts +83 -30
  104. package/core/plugin.ts +3 -0
  105. package/core/types-provisioning.ts +34 -1
  106. package/core/types.ts +15 -2
  107. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  108. package/detail/__tests__/workload.test.ts +3 -152
  109. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  110. package/detail/provisioning.cattle.io.cluster.vue +30 -4
  111. package/detail/workload/index.vue +12 -55
  112. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  113. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
  114. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  115. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  116. package/edit/auth/__tests__/azuread.test.ts +34 -9
  117. package/edit/auth/__tests__/github.test.ts +234 -0
  118. package/edit/auth/__tests__/oidc.test.ts +26 -6
  119. package/edit/auth/__tests__/saml.test.ts +196 -0
  120. package/edit/auth/azuread.vue +128 -95
  121. package/edit/auth/github.vue +72 -13
  122. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  123. package/edit/auth/ldap/config.vue +8 -0
  124. package/edit/auth/ldap/index.vue +75 -1
  125. package/edit/auth/oidc.vue +119 -73
  126. package/edit/auth/saml.vue +76 -12
  127. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  128. package/edit/fleet.cattle.io.helmop.vue +491 -136
  129. package/edit/management.cattle.io.user.vue +5 -2
  130. package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
  131. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  132. package/list/group.principal.vue +5 -4
  133. package/list/harvesterhci.io.management.cluster.vue +8 -9
  134. package/list/management.cattle.io.user.vue +12 -9
  135. package/list/provisioning.cattle.io.cluster.vue +16 -10
  136. package/mixins/__tests__/auth-config.test.ts +90 -0
  137. package/mixins/__tests__/chart.test.ts +94 -0
  138. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  139. package/mixins/auth-config.js +7 -0
  140. package/mixins/chart.js +11 -2
  141. package/mixins/child-hook.js +12 -6
  142. package/mixins/create-edit-view/impl.js +5 -3
  143. package/mixins/resource-fetch-api-pagination.js +21 -1
  144. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  145. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  146. package/models/__tests__/fleet-application.test.ts +175 -0
  147. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  148. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  149. package/models/__tests__/management.cattle.io.node.ts +22 -0
  150. package/models/__tests__/namespace.test.ts +36 -0
  151. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
  152. package/models/__tests__/workload.test.ts +401 -26
  153. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  154. package/models/compliance.cattle.io.clusterscan.js +39 -4
  155. package/models/fleet-application.js +4 -0
  156. package/models/fleet.cattle.io.helmop.js +20 -1
  157. package/models/management.cattle.io.cluster.js +18 -2
  158. package/models/management.cattle.io.node.js +44 -3
  159. package/models/namespace.js +1 -1
  160. package/models/pod.js +33 -1
  161. package/models/provisioning.cattle.io.cluster.js +5 -5
  162. package/models/workload.js +108 -13
  163. package/models/workload.service.js +5 -0
  164. package/package.json +14 -13
  165. package/pages/about.vue +5 -6
  166. package/pages/auth/login.vue +0 -35
  167. package/pages/auth/setup.vue +11 -0
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  172. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  173. package/pages/c/_cluster/apps/charts/install.vue +122 -116
  174. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  175. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  176. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  177. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  178. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  179. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  180. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  181. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  182. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  183. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  184. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  185. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  186. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  187. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  188. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  189. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  190. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  191. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  192. package/pages/c/_cluster/fleet/index.vue +2 -2
  193. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  194. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  195. package/pages/fail-whale.vue +16 -11
  196. package/pages/home.vue +16 -46
  197. package/plugins/clean-html.d.ts +9 -0
  198. package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
  199. package/plugins/dashboard-store/resource-class.js +62 -7
  200. package/plugins/steve/__tests__/actions.test.ts +212 -0
  201. package/plugins/steve/actions.js +96 -0
  202. package/plugins/steve/steve-pagination-utils.ts +1 -1
  203. package/rancher-components/Accordion/Accordion.vue +53 -9
  204. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  205. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  206. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  207. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  208. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  209. package/rancher-components/RcButton/RcButton.vue +94 -15
  210. package/rancher-components/RcButton/types.ts +3 -0
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  213. package/rancher-components/RcSection/RcSection.vue +28 -3
  214. package/scripts/extension/helm/package/Dockerfile +1 -1
  215. package/scripts/test-plugins-build.sh +2 -1
  216. package/store/__tests__/notifications.test.ts +434 -0
  217. package/store/catalog.js +57 -0
  218. package/store/plugins.js +7 -4
  219. package/types/components/buttonGroup.ts +5 -0
  220. package/types/shell/index.d.ts +104 -70
  221. package/utils/__tests__/auth.test.ts +273 -0
  222. package/utils/__tests__/computed.test.ts +193 -0
  223. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  224. package/utils/__tests__/dom.test.ts +81 -0
  225. package/utils/__tests__/duration.test.ts +37 -1
  226. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  227. package/utils/__tests__/fleet-appco.test.ts +312 -0
  228. package/utils/__tests__/monitoring.test.ts +130 -0
  229. package/utils/__tests__/object.test.ts +22 -0
  230. package/utils/__tests__/platform.test.ts +91 -0
  231. package/utils/__tests__/position.test.ts +237 -0
  232. package/utils/__tests__/provider.test.ts +51 -1
  233. package/utils/__tests__/queue.test.ts +232 -0
  234. package/utils/__tests__/release-notes.test.ts +221 -0
  235. package/utils/__tests__/router.test.js +254 -1
  236. package/utils/__tests__/select.test.ts +208 -0
  237. package/utils/__tests__/time.test.ts +265 -1
  238. package/utils/__tests__/title.test.ts +47 -0
  239. package/utils/__tests__/width.test.ts +53 -0
  240. package/utils/__tests__/window.test.ts +158 -0
  241. package/utils/__tests__/xccdf.test.ts +126 -6
  242. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  243. package/utils/crypto/__tests__/index.test.ts +144 -0
  244. package/utils/duration.ts +104 -0
  245. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  246. package/utils/dynamic-content/info.ts +2 -1
  247. package/utils/error.js +13 -0
  248. package/utils/fleet-appco.ts +323 -0
  249. package/utils/object.js +22 -2
  250. package/utils/provider.ts +12 -0
  251. package/utils/validators/__tests__/container-images.test.ts +104 -0
  252. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  253. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  254. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  255. package/utils/xccdf.ts +39 -42
  256. package/vue.config.js +1 -1
  257. package/pages/support/index.vue +0 -264
  258. package/utils/duration.js +0 -43
@@ -0,0 +1,273 @@
1
+ import {
2
+ checkPermissions,
3
+ parseAuthProvidersInfo,
4
+ returnTo,
5
+ } from '@shell/utils/auth';
6
+
7
+ describe('parseAuthProvidersInfo', () => {
8
+ it.each([
9
+ {
10
+ desc: 'empty rows',
11
+ rows: [],
12
+ expected: {
13
+ nonLocal: [],
14
+ enabled: [],
15
+ enabledLocation: null,
16
+ },
17
+ },
18
+ {
19
+ desc: 'only local provider',
20
+ rows: [{
21
+ name: 'local', id: 'local', enabled: true
22
+ }],
23
+ expected: {
24
+ nonLocal: [],
25
+ enabled: [],
26
+ enabledLocation: null,
27
+ },
28
+ },
29
+ {
30
+ desc: 'one disabled non-local provider',
31
+ rows: [{
32
+ name: 'github', id: 'github', enabled: false
33
+ }],
34
+ expected: {
35
+ nonLocal: [{
36
+ name: 'github', id: 'github', enabled: false
37
+ }],
38
+ enabled: [],
39
+ enabledLocation: null,
40
+ },
41
+ },
42
+ {
43
+ desc: 'one enabled non-local provider',
44
+ rows: [{
45
+ name: 'github', id: 'github', enabled: true
46
+ }],
47
+ expected: {
48
+ nonLocal: [{
49
+ name: 'github', id: 'github', enabled: true
50
+ }],
51
+ enabled: [{
52
+ name: 'github', id: 'github', enabled: true
53
+ }],
54
+ enabledLocation: {
55
+ name: 'c-cluster-auth-config-id',
56
+ params: { id: 'github' },
57
+ query: { mode: 'edit' },
58
+ },
59
+ },
60
+ },
61
+ {
62
+ desc: 'oidc provider excluded from nonLocal but included in enabled',
63
+ rows: [{
64
+ name: 'oidc', id: 'oidc', enabled: true
65
+ }],
66
+ expected: {
67
+ nonLocal: [],
68
+ enabled: [{
69
+ name: 'oidc', id: 'oidc', enabled: true
70
+ }],
71
+ enabledLocation: {
72
+ name: 'c-cluster-auth-config-id',
73
+ params: { id: 'oidc' },
74
+ query: { mode: 'edit' },
75
+ },
76
+ },
77
+ },
78
+ {
79
+ desc: 'two enabled non-local providers gives null enabledLocation',
80
+ rows: [
81
+ {
82
+ name: 'github', id: 'github', enabled: true
83
+ },
84
+ {
85
+ name: 'activedirectory', id: 'activedirectory', enabled: true
86
+ },
87
+ ],
88
+ expected: {
89
+ nonLocal: [
90
+ {
91
+ name: 'github', id: 'github', enabled: true
92
+ },
93
+ {
94
+ name: 'activedirectory', id: 'activedirectory', enabled: true
95
+ },
96
+ ],
97
+ enabled: [
98
+ {
99
+ name: 'github', id: 'github', enabled: true
100
+ },
101
+ {
102
+ name: 'activedirectory', id: 'activedirectory', enabled: true
103
+ },
104
+ ],
105
+ enabledLocation: null,
106
+ },
107
+ },
108
+ {
109
+ desc: 'local provider excluded while non-local disabled provider is retained',
110
+ rows: [
111
+ {
112
+ name: 'local', id: 'local', enabled: true
113
+ },
114
+ {
115
+ name: 'github', id: 'github', enabled: false
116
+ },
117
+ ],
118
+ expected: {
119
+ nonLocal: [{
120
+ name: 'github', id: 'github', enabled: false
121
+ }],
122
+ enabled: [],
123
+ enabledLocation: null,
124
+ },
125
+ },
126
+ ])('returns provider info for $desc', ({ rows, expected }) => {
127
+ expect(parseAuthProvidersInfo(rows)).toStrictEqual(expected);
128
+ });
129
+ });
130
+
131
+ describe('checkPermissions', () => {
132
+ it('returns empty object for empty types', async() => {
133
+ const getters = { 'management/schemaFor': jest.fn() };
134
+ const result = await checkPermissions({}, getters);
135
+
136
+ expect(result).toStrictEqual({});
137
+ });
138
+
139
+ it('returns false when schema is not found', async() => {
140
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(null) };
141
+ const result = await checkPermissions({ pods: { type: 'pod' } }, getters);
142
+
143
+ expect(result).toStrictEqual({ pods: false });
144
+ expect(getters['management/schemaFor']).toHaveBeenCalledWith('pod');
145
+ });
146
+
147
+ it('returns true when schema exists with no method constraints', async() => {
148
+ const mockSchema = { resourceMethods: ['GET', 'PUT'] };
149
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(mockSchema) };
150
+ const result = await checkPermissions({ pods: { type: 'pod' } }, getters);
151
+
152
+ expect(result).toStrictEqual({ pods: true });
153
+ });
154
+
155
+ it('uses schemaValidator result when provided', async() => {
156
+ const mockSchema = { resourceMethods: ['GET'] };
157
+ const schemaValidator = jest.fn().mockReturnValue(false);
158
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(mockSchema) };
159
+ const result = await checkPermissions({ pods: { type: 'pod', schemaValidator } }, getters);
160
+
161
+ expect(result).toStrictEqual({ pods: false });
162
+ expect(schemaValidator).toHaveBeenCalledWith(mockSchema);
163
+ });
164
+
165
+ it('returns true when all resourceMethods are available', async() => {
166
+ const mockSchema = { resourceMethods: ['GET', 'PUT', 'DELETE'] };
167
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(mockSchema) };
168
+ const types = { pods: { type: 'pod', resourceMethods: ['GET', 'PUT'] } };
169
+ const result = await checkPermissions(types, getters);
170
+
171
+ expect(result).toStrictEqual({ pods: true });
172
+ });
173
+
174
+ it('returns false when a resourceMethod is not in schema', async() => {
175
+ const mockSchema = { resourceMethods: ['GET'] };
176
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(mockSchema) };
177
+ const types = { pods: { type: 'pod', resourceMethods: ['GET', 'DELETE'] } };
178
+ const result = await checkPermissions(types, getters);
179
+
180
+ expect(result).toStrictEqual({ pods: false });
181
+ });
182
+
183
+ it('returns false when schema has no resourceMethods and type requires them', async() => {
184
+ const mockSchema = {};
185
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(mockSchema) };
186
+ const types = { pods: { type: 'pod', resourceMethods: ['GET'] } };
187
+ const result = await checkPermissions(types, getters);
188
+
189
+ expect(result).toStrictEqual({ pods: false });
190
+ });
191
+
192
+ it('returns true when all collectionMethods are available', async() => {
193
+ const mockSchema = { collectionMethods: ['GET', 'POST'] };
194
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(mockSchema) };
195
+ const types = { pods: { type: 'pod', collectionMethods: ['GET'] } };
196
+ const result = await checkPermissions(types, getters);
197
+
198
+ expect(result).toStrictEqual({ pods: true });
199
+ });
200
+
201
+ it('returns false when a collectionMethod is not in schema', async() => {
202
+ const mockSchema = { collectionMethods: ['GET'] };
203
+ const getters = { 'management/schemaFor': jest.fn().mockReturnValue(mockSchema) };
204
+ const types = { pods: { type: 'pod', collectionMethods: ['GET', 'DELETE'] } };
205
+ const result = await checkPermissions(types, getters);
206
+
207
+ expect(result).toStrictEqual({ pods: false });
208
+ });
209
+
210
+ it('handles multiple types independently', async() => {
211
+ const getters = {
212
+ 'management/schemaFor': jest.fn()
213
+ .mockReturnValueOnce({ resourceMethods: ['GET'] })
214
+ .mockReturnValueOnce(null),
215
+ };
216
+ const types = {
217
+ pods: { type: 'pod' },
218
+ nodes: { type: 'node' },
219
+ };
220
+ const result = await checkPermissions(types, getters);
221
+
222
+ expect(result).toStrictEqual({ pods: true, nodes: false });
223
+ });
224
+ });
225
+
226
+ describe('returnTo', () => {
227
+ it.each([
228
+ {
229
+ desc: 'default route when no route in opt',
230
+ opt: {},
231
+ vm: { $router: {} },
232
+ expected: 'http://localhost/auth/verify',
233
+ },
234
+ {
235
+ desc: 'custom route from opt',
236
+ opt: { route: '/my/page' },
237
+ vm: { $router: {} },
238
+ expected: 'http://localhost/my/page',
239
+ },
240
+ {
241
+ desc: 'router base prepended to route',
242
+ opt: {},
243
+ vm: { $router: { options: { base: '/ui' } } },
244
+ expected: 'http://localhost/ui/auth/verify',
245
+ },
246
+ {
247
+ desc: 'router base of "/" does not alter route',
248
+ opt: {},
249
+ vm: { $router: { options: { base: '/' } } },
250
+ expected: 'http://localhost/auth/verify',
251
+ },
252
+ {
253
+ desc: 'backTo option adds back-to query param',
254
+ opt: { backTo: 'dashboard' },
255
+ vm: { $router: {} },
256
+ expected: 'http://localhost/auth/verify?back-to=dashboard',
257
+ },
258
+ {
259
+ desc: 'config option adds config query param',
260
+ opt: { config: 'github' },
261
+ vm: { $router: {} },
262
+ expected: 'http://localhost/auth/verify?config=github',
263
+ },
264
+ {
265
+ desc: 'isSlo option adds is-slo and logged-out params',
266
+ opt: { isSlo: true },
267
+ vm: { $router: {} },
268
+ expected: 'http://localhost/auth/verify?is-slo&logged-out',
269
+ },
270
+ ])('builds return URL for $desc', ({ opt, vm, expected }) => {
271
+ expect(returnTo(opt, vm)).toStrictEqual(expected);
272
+ });
273
+ });
@@ -0,0 +1,193 @@
1
+ import { integerString, keyValueStrings } from '@shell/utils/computed';
2
+
3
+ describe('integerString', () => {
4
+ describe('get', () => {
5
+ it.each([
6
+ {
7
+ desc: 'integer string',
8
+ initial: '42',
9
+ expected: 42,
10
+ },
11
+ {
12
+ desc: 'float string',
13
+ initial: '3.14',
14
+ expected: 3.14,
15
+ },
16
+ {
17
+ desc: 'zero string',
18
+ initial: '0',
19
+ expected: 0,
20
+ },
21
+ {
22
+ desc: 'negative integer string',
23
+ initial: '-7',
24
+ expected: -7,
25
+ },
26
+ {
27
+ desc: 'undefined value',
28
+ initial: undefined as unknown as string,
29
+ expected: NaN,
30
+ },
31
+ ])('returns a number for $desc', ({ initial, expected }) => {
32
+ const computed = integerString('value');
33
+ const ctx = { value: initial };
34
+
35
+ expect(computed.get.call(ctx)).toStrictEqual(expected);
36
+ });
37
+
38
+ it('reads from a nested dot-notation path', () => {
39
+ const computed = integerString('a.b');
40
+ const ctx = { a: { b: '99' } };
41
+
42
+ expect(computed.get.call(ctx)).toStrictEqual(99);
43
+ });
44
+ });
45
+
46
+ describe('set', () => {
47
+ it.each([
48
+ {
49
+ desc: 'positive integer',
50
+ input: 42,
51
+ expected: '42',
52
+ },
53
+ {
54
+ desc: 'float',
55
+ input: 3.14,
56
+ expected: '3.14',
57
+ },
58
+ {
59
+ desc: 'zero',
60
+ input: 0,
61
+ expected: '0',
62
+ },
63
+ {
64
+ desc: 'negative integer',
65
+ input: -7,
66
+ expected: '-7',
67
+ },
68
+ ])('stores a string representation for $desc', ({ input, expected }) => {
69
+ const computed = integerString('value');
70
+ const ctx = { value: '' };
71
+
72
+ computed.set.call(ctx, input);
73
+
74
+ expect(ctx.value).toStrictEqual(expected);
75
+ });
76
+
77
+ it('writes to a nested dot-notation path', () => {
78
+ const computed = integerString('a.b');
79
+ const ctx = { a: { b: '' } };
80
+
81
+ computed.set.call(ctx, 5);
82
+
83
+ expect((ctx.a as { b: string }).b).toStrictEqual('5');
84
+ });
85
+ });
86
+ });
87
+
88
+ describe('keyValueStrings', () => {
89
+ describe('get', () => {
90
+ it('returns an empty object for an empty array', () => {
91
+ const computed = keyValueStrings('items');
92
+ const ctx = { items: [] as string[] };
93
+
94
+ expect(computed.get.call(ctx)).toStrictEqual({});
95
+ });
96
+
97
+ it('returns an empty object when path value is undefined', () => {
98
+ const computed = keyValueStrings('items');
99
+ const ctx = { items: undefined as unknown as string[] };
100
+
101
+ expect(computed.get.call(ctx)).toStrictEqual({});
102
+ });
103
+
104
+ it.each([
105
+ {
106
+ desc: 'single entry with default delimiter',
107
+ entries: ['key=value'],
108
+ delimiter: '=',
109
+ expected: { key: 'value' },
110
+ },
111
+ {
112
+ desc: 'multiple entries with default delimiter',
113
+ entries: ['foo=bar', 'baz=qux'],
114
+ delimiter: '=',
115
+ expected: {
116
+ foo: 'bar',
117
+ baz: 'qux',
118
+ },
119
+ },
120
+ {
121
+ desc: 'single entry with custom colon delimiter',
122
+ entries: ['key:value'],
123
+ delimiter: ':',
124
+ expected: { key: 'value' },
125
+ },
126
+ {
127
+ desc: 'multiple entries with custom colon delimiter',
128
+ entries: ['a:1', 'b:2'],
129
+ delimiter: ':',
130
+ expected: {
131
+ a: '1',
132
+ b: '2',
133
+ },
134
+ },
135
+ ])('returns an object for $desc', ({ entries, delimiter, expected }) => {
136
+ const computed = keyValueStrings('items', delimiter);
137
+ const ctx = { items: entries };
138
+
139
+ expect(computed.get.call(ctx)).toStrictEqual(expected);
140
+ });
141
+ });
142
+
143
+ describe('set', () => {
144
+ it('stores an empty array for an empty object', () => {
145
+ const computed = keyValueStrings('items');
146
+ const ctx = { items: ['existing=val'] };
147
+
148
+ computed.set.call(ctx, {});
149
+
150
+ expect(ctx.items).toStrictEqual([]);
151
+ });
152
+
153
+ it.each([
154
+ {
155
+ desc: 'single key-value pair with default delimiter',
156
+ input: { key: 'value' },
157
+ delimiter: '=',
158
+ expected: ['key=value'],
159
+ },
160
+ {
161
+ desc: 'multiple key-value pairs with default delimiter',
162
+ input: {
163
+ foo: 'bar',
164
+ baz: 'qux',
165
+ },
166
+ delimiter: '=',
167
+ expected: ['foo=bar', 'baz=qux'],
168
+ },
169
+ {
170
+ desc: 'single pair with custom colon delimiter',
171
+ input: { key: 'value' },
172
+ delimiter: ':',
173
+ expected: ['key:value'],
174
+ },
175
+ {
176
+ desc: 'multiple pairs with custom colon delimiter',
177
+ input: {
178
+ a: '1',
179
+ b: '2',
180
+ },
181
+ delimiter: ':',
182
+ expected: ['a:1', 'b:2'],
183
+ },
184
+ ])('stores delimited strings for $desc', ({ input, delimiter, expected }) => {
185
+ const computed = keyValueStrings('items', delimiter);
186
+ const ctx = { items: [] as string[] };
187
+
188
+ computed.set.call(ctx, input);
189
+
190
+ expect(ctx.items).toStrictEqual(expected);
191
+ });
192
+ });
193
+ });
@@ -0,0 +1,163 @@
1
+ import CspAdapterUtils from '@shell/utils/cspAdaptor';
2
+ import { CATALOG } from '@shell/config/types';
3
+
4
+ type App = { metadata?: { name?: string } };
5
+
6
+ function makeStore({
7
+ canList = true,
8
+ paginationEnabled = false,
9
+ findAllResult = [] as App[],
10
+ findPageData = [] as App[],
11
+ } = {}) {
12
+ return {
13
+ getters: {
14
+ 'management/canList': jest.fn().mockReturnValue(canList),
15
+ 'management/paginationEnabled': jest.fn().mockReturnValue(paginationEnabled),
16
+ },
17
+ dispatch: jest.fn((action: string) => {
18
+ if (action === 'management/findAll') {
19
+ return Promise.resolve(findAllResult);
20
+ }
21
+ if (action === 'management/findPage') {
22
+ return Promise.resolve({ data: findPageData });
23
+ }
24
+
25
+ return Promise.resolve(null);
26
+ }),
27
+ };
28
+ }
29
+
30
+ describe('cspAdaptor', () => {
31
+ describe('hasCspAdapter', () => {
32
+ it.each([
33
+ {
34
+ desc: 'returns the matching app for rancher-csp-adapter',
35
+ apps: [{ metadata: { name: 'rancher-csp-adapter' } }, { metadata: { name: 'other-app' } }],
36
+ expected: { metadata: { name: 'rancher-csp-adapter' } },
37
+ },
38
+ {
39
+ desc: 'returns the matching app for rancher-csp-billing-adapter',
40
+ apps: [{ metadata: { name: 'rancher-csp-billing-adapter' } }],
41
+ expected: { metadata: { name: 'rancher-csp-billing-adapter' } },
42
+ },
43
+ {
44
+ desc: 'returns first match when multiple csp apps are present',
45
+ apps: [{ metadata: { name: 'rancher-csp-billing-adapter' } }, { metadata: { name: 'rancher-csp-adapter' } }],
46
+ expected: { metadata: { name: 'rancher-csp-billing-adapter' } },
47
+ },
48
+ {
49
+ desc: 'returns undefined when no app name matches',
50
+ apps: [{ metadata: { name: 'unrelated-app' } }],
51
+ expected: undefined,
52
+ },
53
+ {
54
+ desc: 'returns undefined for apps without metadata',
55
+ apps: [{}],
56
+ expected: undefined,
57
+ },
58
+ {
59
+ desc: 'returns undefined when apps is an empty array',
60
+ apps: [],
61
+ expected: undefined,
62
+ },
63
+ {
64
+ desc: 'returns undefined when apps is null',
65
+ apps: null as unknown as App[],
66
+ expected: undefined,
67
+ },
68
+ {
69
+ desc: 'returns undefined when apps is undefined',
70
+ apps: undefined as unknown as App[],
71
+ expected: undefined,
72
+ },
73
+ ])('$desc', ({ apps, expected }) => {
74
+ const result = CspAdapterUtils.hasCspAdapter({ $store: {} as any, apps });
75
+
76
+ expect(result).toStrictEqual(expected);
77
+ });
78
+ });
79
+
80
+ describe('fetchCspAdaptorApp', () => {
81
+ beforeEach(() => {
82
+ CspAdapterUtils.resetState();
83
+ });
84
+
85
+ it('returns empty array when canList returns false', async() => {
86
+ const store = makeStore({ canList: false });
87
+ const result = await CspAdapterUtils.fetchCspAdaptorApp(store as any);
88
+
89
+ expect(result).toStrictEqual([]);
90
+ expect(store.dispatch).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it('uses findAll when canList is true and pagination is disabled', async() => {
94
+ const findAllResult: App[] = [{ metadata: { name: 'rancher-csp-adapter' } }];
95
+ const store = makeStore({
96
+ canList: true, paginationEnabled: false, findAllResult
97
+ });
98
+
99
+ const result = await CspAdapterUtils.fetchCspAdaptorApp(store as any);
100
+
101
+ expect(result).toStrictEqual(findAllResult);
102
+ expect(store.dispatch).toHaveBeenCalledWith('management/findAll', { type: CATALOG.APP });
103
+ });
104
+
105
+ it('uses findPage when canList is true and pagination is enabled', async() => {
106
+ const findPageData: App[] = [{ metadata: { name: 'rancher-csp-billing-adapter' } }];
107
+ const store = makeStore({
108
+ canList: true, paginationEnabled: true, findPageData
109
+ });
110
+
111
+ const result = await CspAdapterUtils.fetchCspAdaptorApp(store as any);
112
+
113
+ expect(result).toStrictEqual(findPageData);
114
+ expect(store.dispatch).toHaveBeenCalledWith('management/findPage', expect.objectContaining({ type: CATALOG.APP }));
115
+ });
116
+
117
+ it('caches the result and does not dispatch again on a second call', async() => {
118
+ const findAllResult: App[] = [{ metadata: { name: 'rancher-csp-adapter' } }];
119
+ const store = makeStore({
120
+ canList: true, paginationEnabled: false, findAllResult
121
+ });
122
+
123
+ await CspAdapterUtils.fetchCspAdaptorApp(store as any);
124
+ const result = await CspAdapterUtils.fetchCspAdaptorApp(store as any);
125
+
126
+ expect(result).toStrictEqual(findAllResult);
127
+ expect(store.dispatch).toHaveBeenCalledTimes(1);
128
+ });
129
+
130
+ it('fetches fresh data after resetState clears the cache', async() => {
131
+ const findAllResult: App[] = [{ metadata: { name: 'rancher-csp-adapter' } }];
132
+ const store = makeStore({
133
+ canList: true, paginationEnabled: false, findAllResult
134
+ });
135
+
136
+ await CspAdapterUtils.fetchCspAdaptorApp(store as any);
137
+
138
+ CspAdapterUtils.resetState();
139
+
140
+ const freshStore = makeStore({
141
+ canList: true, paginationEnabled: false, findAllResult: []
142
+ });
143
+ const result = await CspAdapterUtils.fetchCspAdaptorApp(freshStore as any);
144
+
145
+ expect(result).toStrictEqual([]);
146
+ expect(freshStore.dispatch).toHaveBeenCalledTimes(1);
147
+ });
148
+
149
+ it('passes pagination filters for known csp adapter app names when using findPage', async() => {
150
+ const store = makeStore({
151
+ canList: true, paginationEnabled: true, findPageData: []
152
+ });
153
+
154
+ await CspAdapterUtils.fetchCspAdaptorApp(store as any);
155
+
156
+ const [, args] = store.dispatch.mock.calls[0];
157
+
158
+ expect(args.type).toStrictEqual(CATALOG.APP);
159
+ expect(args.opt.watch).toStrictEqual(false);
160
+ expect(args.opt.transient).toStrictEqual(true);
161
+ });
162
+ });
163
+ });
@@ -0,0 +1,81 @@
1
+ import { getParent } from '@shell/utils/dom';
2
+
3
+ describe('getParent', () => {
4
+ it('returns null when element has no parent', () => {
5
+ const el = document.createElement('div');
6
+
7
+ expect(getParent(el, 'div')).toBeNull();
8
+ });
9
+
10
+ it('returns null when el is null', () => {
11
+ expect(getParent(null, 'div')).toBeNull();
12
+ });
13
+
14
+ it('returns null when el is undefined', () => {
15
+ expect(getParent(undefined, 'div')).toBeNull();
16
+ });
17
+
18
+ it('returns the direct parent matching the selector', () => {
19
+ const parent = document.createElement('section');
20
+ const child = document.createElement('div');
21
+
22
+ parent.appendChild(child);
23
+
24
+ expect(getParent(child, 'section')).toBe(parent);
25
+ });
26
+
27
+ it('returns an ancestor matching the selector when direct parent does not match', () => {
28
+ const grandparent = document.createElement('article');
29
+ const parent = document.createElement('div');
30
+ const child = document.createElement('span');
31
+
32
+ grandparent.appendChild(parent);
33
+ parent.appendChild(child);
34
+
35
+ expect(getParent(child, 'article')).toBe(grandparent);
36
+ });
37
+
38
+ it('returns null when no ancestor matches the selector', () => {
39
+ const parent = document.createElement('div');
40
+ const child = document.createElement('span');
41
+
42
+ parent.appendChild(child);
43
+
44
+ expect(getParent(child, 'article')).toBeNull();
45
+ });
46
+
47
+ it('matches by class selector', () => {
48
+ const parent = document.createElement('div');
49
+
50
+ parent.className = 'my-container';
51
+ const child = document.createElement('span');
52
+
53
+ parent.appendChild(child);
54
+
55
+ expect(getParent(child, '.my-container')).toBe(parent);
56
+ });
57
+
58
+ it('matches by id selector', () => {
59
+ const parent = document.createElement('div');
60
+
61
+ parent.id = 'root';
62
+ const child = document.createElement('span');
63
+
64
+ parent.appendChild(child);
65
+
66
+ expect(getParent(child, '#root')).toBe(parent);
67
+ });
68
+
69
+ it('skips intermediate non-matching ancestors and finds the correct one', () => {
70
+ const great = document.createElement('nav');
71
+ const grandparent = document.createElement('section');
72
+ const parent = document.createElement('div');
73
+ const child = document.createElement('span');
74
+
75
+ great.appendChild(grandparent);
76
+ grandparent.appendChild(parent);
77
+ parent.appendChild(child);
78
+
79
+ expect(getParent(child, 'nav')).toBe(great);
80
+ });
81
+ });