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

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 (272) hide show
  1. package/apis/impl/apis.ts +6 -0
  2. package/apis/index.ts +26 -0
  3. package/apis/intf/resources-api/cluster-api.ts +18 -0
  4. package/apis/intf/resources-api/mgmt-api.ts +15 -0
  5. package/apis/intf/resources-api/resource-base.ts +107 -0
  6. package/apis/intf/resources-api/resource-constants.ts +147 -0
  7. package/apis/intf/resources-api/resources-api.ts +143 -0
  8. package/apis/intf/resources.ts +49 -0
  9. package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
  10. package/apis/intf/shell-api/proxy.ts +216 -0
  11. package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
  12. package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
  13. package/apis/intf/shell.ts +12 -6
  14. package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
  15. package/apis/resources/index.ts +22 -0
  16. package/apis/resources/resources-api-class.ts +187 -0
  17. package/apis/shell/__tests__/proxy.test.ts +369 -0
  18. package/apis/shell/index.ts +8 -1
  19. package/apis/shell/modal.ts +4 -1
  20. package/apis/shell/notifications.ts +9 -6
  21. package/apis/shell/proxy.ts +256 -0
  22. package/apis/shell/slide-in.ts +4 -1
  23. package/apis/vue-shim.d.ts +2 -1
  24. package/assets/data/aws-regions.json +4 -0
  25. package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
  26. package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
  27. package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
  28. package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
  29. package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
  30. package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
  31. package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
  32. package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
  33. package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
  34. package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
  35. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
  36. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
  37. package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
  38. package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
  39. package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
  40. package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
  41. package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
  42. package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
  43. package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
  44. package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
  45. package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
  46. package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
  47. package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
  48. package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
  49. package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
  50. package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
  51. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
  52. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
  53. package/assets/styles/base/_variables.scss +2 -0
  54. package/assets/styles/fonts/_fontstack.scss +132 -8
  55. package/assets/translations/en-us.yaml +22 -5
  56. package/chart/monitoring/index.vue +10 -1
  57. package/components/ActionDropdownShell.vue +2 -1
  58. package/components/CruResourceFooter.vue +9 -5
  59. package/components/ExplorerProjectsNamespaces.vue +1 -1
  60. package/components/InstallHelmCharts.vue +2 -2
  61. package/components/LandingPagePreference.vue +14 -5
  62. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  63. package/components/Resource/Detail/Metadata/index.vue +6 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  65. package/components/Resource/Detail/SpacedRow.vue +3 -1
  66. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  67. package/components/ResourceList/Masthead.vue +12 -8
  68. package/components/SelectIconGrid.vue +0 -10
  69. package/components/SingleClusterInfo.vue +1 -0
  70. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  71. package/components/SortableTable/index.vue +6 -9
  72. package/components/SortableTable/selection.js +23 -5
  73. package/components/SortableTable/sorting.js +6 -3
  74. package/components/Wizard.vue +14 -13
  75. package/components/fleet/FleetBundles.vue +100 -12
  76. package/components/fleet/FleetClusterTargets/index.vue +37 -15
  77. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  78. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  79. package/components/form/LabeledSelect.vue +20 -3
  80. package/components/form/NameNsDescription.vue +11 -0
  81. package/components/form/Security.vue +6 -2
  82. package/components/form/WorkloadPorts.vue +2 -7
  83. package/components/form/__tests__/Security.test.ts +76 -0
  84. package/components/formatter/Autoscaler.vue +4 -4
  85. package/components/formatter/ClusterKubeVersion.vue +27 -0
  86. package/components/formatter/ClusterLink.vue +1 -7
  87. package/components/formatter/ClusterProvider.vue +6 -10
  88. package/components/formatter/FleetSummaryGraph.vue +0 -3
  89. package/components/formatter/MachineSummaryGraph.vue +1 -1
  90. package/components/formatter/PodsUsage.vue +2 -2
  91. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  92. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  93. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  94. package/components/nav/NamespaceFilter.vue +2 -2
  95. package/components/nav/TopLevelMenu.helper.ts +15 -3
  96. package/components/nav/TopLevelMenu.vue +16 -5
  97. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  98. package/components/templates/home.vue +18 -0
  99. package/components/templates/plain.vue +18 -0
  100. package/components/templates/standalone.vue +17 -0
  101. package/composables/useFormValidation.ts +93 -0
  102. package/composables/useVeeValidateField.test.ts +159 -0
  103. package/composables/useVeeValidateField.ts +67 -0
  104. package/config/pagination-table-headers.js +18 -1
  105. package/config/product/manager.js +82 -21
  106. package/config/router/routes.js +6 -0
  107. package/config/table-headers.js +20 -1
  108. package/config/types.js +2 -1
  109. package/core/__tests__/plugin-products.test.ts +904 -20
  110. package/core/plugin-products-base.ts +107 -7
  111. package/core/plugin-products.ts +4 -0
  112. package/core/plugin-types.ts +111 -1
  113. package/core/plugin.ts +15 -7
  114. package/core/productDebugger.js +9 -4
  115. package/core/types-provisioning.ts +43 -30
  116. package/core/types.ts +57 -20
  117. package/detail/__tests__/pod.test.ts +41 -0
  118. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  119. package/detail/pod.vue +1 -1
  120. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  121. package/edit/auth/__tests__/azuread.test.ts +217 -34
  122. package/edit/auth/azuread.vue +122 -14
  123. package/edit/auth/oidc.vue +2 -2
  124. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  125. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  126. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  127. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  128. package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
  129. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  130. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  131. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  132. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  133. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  134. package/edit/secret/__tests__/ssh.test.ts +5 -6
  135. package/edit/secret/basic.vue +31 -0
  136. package/edit/secret/index.vue +68 -17
  137. package/edit/secret/registry.vue +38 -0
  138. package/edit/secret/ssh.vue +29 -0
  139. package/edit/secret/tls.vue +30 -0
  140. package/edit/service.vue +4 -4
  141. package/edit/workload/Upgrading.vue +3 -3
  142. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  143. package/edit/workload/mixins/workload.js +2 -1
  144. package/list/fleet.cattle.io.bundle.vue +7 -104
  145. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  146. package/list/provisioning.cattle.io.cluster.vue +262 -180
  147. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  148. package/mixins/__tests__/chart.test.ts +112 -0
  149. package/mixins/brand.js +2 -1
  150. package/mixins/chart.js +12 -8
  151. package/mixins/resource-fetch-api-pagination.js +41 -5
  152. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  153. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  154. package/models/__tests__/management.cattle.io.node.ts +6 -5
  155. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  156. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  157. package/models/base-cluster.x-k8s.io.js +26 -0
  158. package/models/cluster.js +1 -1
  159. package/models/cluster.x-k8s.io.machine.js +4 -22
  160. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  161. package/models/cluster.x-k8s.io.machineset.js +2 -20
  162. package/models/compliance.cattle.io.clusterscan.js +130 -2
  163. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  164. package/models/fleet-application.js +3 -1
  165. package/models/management.cattle.io.cluster.js +417 -40
  166. package/models/management.cattle.io.node.js +6 -4
  167. package/models/management.cattle.io.nodepool.js +1 -1
  168. package/models/networking.k8s.io.ingress.js +12 -4
  169. package/models/provisioning.cattle.io.cluster.js +47 -330
  170. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  171. package/package.json +11 -29
  172. package/pages/__tests__/readme.test.ts +49 -0
  173. package/pages/auth/setup.vue +2 -3
  174. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
  175. package/pages/c/_cluster/apps/charts/chart.vue +60 -8
  176. package/pages/c/_cluster/apps/charts/install.vue +10 -7
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  178. package/pages/c/_cluster/explorer/index.vue +5 -49
  179. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  180. package/pages/c/_cluster/istio/index.vue +21 -6
  181. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
  182. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
  183. package/pages/c/_cluster/uiplugins/index.vue +203 -197
  184. package/pages/diagnostic.vue +13 -17
  185. package/pages/fail-whale.vue +18 -0
  186. package/pages/home.vue +77 -260
  187. package/pages/readme.vue +88 -0
  188. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  189. package/plugins/dashboard-store/actions.js +40 -18
  190. package/plugins/dashboard-store/resource-class.js +5 -2
  191. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  192. package/plugins/steve/steve-pagination-utils.ts +11 -3
  193. package/plugins/steve/subscribe.js +35 -5
  194. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
  195. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
  196. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  197. package/rancher-components/RcButton/RcButton.vue +38 -8
  198. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  199. package/store/__tests__/catalog.test.ts +115 -1
  200. package/store/__tests__/type-map.test.ts +556 -1
  201. package/store/action-menu.js +8 -3
  202. package/store/auth.js +1 -1
  203. package/store/aws.js +27 -16
  204. package/store/catalog.js +27 -3
  205. package/store/digitalocean.js +20 -38
  206. package/store/index.js +2 -0
  207. package/store/linode.js +25 -40
  208. package/store/pnap.js +1 -0
  209. package/store/type-map.js +111 -29
  210. package/tsconfig.paths.json +8 -8
  211. package/types/kube/kube-api.ts +14 -1
  212. package/types/rancher/steve.api.ts +12 -12
  213. package/types/resources/settings.d.ts +2 -1
  214. package/types/shell/index.d.ts +102 -2
  215. package/types/store/dashboard-store.types.ts +108 -11
  216. package/types/store/pagination.types.ts +6 -3
  217. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  218. package/utils/__tests__/async.test.ts +87 -0
  219. package/utils/__tests__/aws.test.ts +140 -0
  220. package/utils/__tests__/banners.test.ts +176 -0
  221. package/utils/__tests__/chart.test.ts +64 -1
  222. package/utils/__tests__/color.test.ts +226 -0
  223. package/utils/__tests__/duration.test.ts +140 -0
  224. package/utils/__tests__/fleet.test.ts +340 -0
  225. package/utils/__tests__/ingress.test.ts +553 -0
  226. package/utils/__tests__/kube.test.ts +68 -0
  227. package/utils/__tests__/namespace-filter.test.ts +109 -0
  228. package/utils/__tests__/pagination-utils.test.ts +361 -0
  229. package/utils/__tests__/parse-externalid.test.ts +137 -0
  230. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  231. package/utils/__tests__/poller-sequential.test.ts +177 -0
  232. package/utils/__tests__/poller.test.ts +170 -0
  233. package/utils/__tests__/promise.test.ts +346 -0
  234. package/utils/__tests__/settings.test.ts +140 -0
  235. package/utils/__tests__/sort-utils.test.ts +301 -0
  236. package/utils/__tests__/string-utils.test.ts +798 -0
  237. package/utils/__tests__/string.test.ts +23 -1
  238. package/utils/__tests__/style.test.ts +154 -0
  239. package/utils/__tests__/svg-filter.test.ts +184 -0
  240. package/utils/__tests__/units.test.ts +417 -0
  241. package/utils/__tests__/versions.test.ts +128 -0
  242. package/utils/__tests__/xccdf.test.ts +391 -0
  243. package/utils/chart.js +36 -0
  244. package/utils/fleet.ts +13 -3
  245. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  246. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  247. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  248. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  249. package/utils/gc/__tests__/gc.test.ts +487 -0
  250. package/utils/ingress.ts +9 -1
  251. package/utils/pagination-utils.ts +2 -1
  252. package/utils/string.js +25 -2
  253. package/utils/uiplugins.ts +5 -5
  254. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  255. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  256. package/utils/validators/__tests__/index.test.ts +481 -0
  257. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  258. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  259. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  260. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  261. package/utils/validators/__tests__/role-template.test.ts +149 -0
  262. package/utils/validators/__tests__/service.test.ts +283 -0
  263. package/utils/validators/__tests__/setting.test.js +32 -0
  264. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  265. package/utils/validators/formRules/index.ts +5 -5
  266. package/utils/validators/machine-pool.ts +1 -1
  267. package/utils/validators/setting.js +18 -3
  268. package/utils/xccdf.ts +418 -0
  269. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  270. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  271. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  272. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
@@ -0,0 +1,163 @@
1
+ import { validateKubernetesName } from '@shell/utils/validators/kubernetes-name';
2
+
3
+ function makeGetters(): Record<string, unknown> {
4
+ return {
5
+ 'i18n/t': (key: string, args?: unknown) => (args ? `${ key }:${ JSON.stringify(args) }` : key),
6
+ 'i18n/exists': () => false,
7
+ };
8
+ }
9
+
10
+ describe('validateKubernetesName', () => {
11
+ describe('valid names', () => {
12
+ it('accepts a simple lowercase name', () => {
13
+ const errors = validateKubernetesName('my-app', 'Name', makeGetters(), {});
14
+
15
+ expect(errors).toStrictEqual([]);
16
+ });
17
+
18
+ it('accepts alphanumeric name', () => {
19
+ const errors = validateKubernetesName('abc123', 'Name', makeGetters(), {});
20
+
21
+ expect(errors).toStrictEqual([]);
22
+ });
23
+
24
+ it('accepts single character', () => {
25
+ const errors = validateKubernetesName('a', 'Name', makeGetters(), {});
26
+
27
+ expect(errors).toStrictEqual([]);
28
+ });
29
+
30
+ it('accepts name at max length (63)', () => {
31
+ const name = 'a'.repeat(63);
32
+ const errors = validateKubernetesName(name, 'Name', makeGetters(), {});
33
+
34
+ expect(errors).toStrictEqual([]);
35
+ });
36
+
37
+ it('accepts uppercase letters by default validChars', () => {
38
+ const errors = validateKubernetesName('MyApp', 'Name', makeGetters(), {});
39
+
40
+ expect(errors).toStrictEqual([]);
41
+ });
42
+ });
43
+
44
+ describe('invalid characters', () => {
45
+ it('rejects underscores', () => {
46
+ const errors = validateKubernetesName('my_app', 'Name', makeGetters(), {});
47
+
48
+ expect(errors.length).toBeGreaterThan(0);
49
+ });
50
+
51
+ it('rejects dots', () => {
52
+ const errors = validateKubernetesName('my.app', 'Name', makeGetters(), {});
53
+
54
+ expect(errors.length).toBeGreaterThan(0);
55
+ });
56
+
57
+ it('rejects spaces', () => {
58
+ const errors = validateKubernetesName('my app', 'Name', makeGetters(), {});
59
+
60
+ expect(errors.length).toBeGreaterThan(0);
61
+ });
62
+ });
63
+
64
+ describe('hyphen position rules', () => {
65
+ it('rejects name starting with hyphen', () => {
66
+ const errors = validateKubernetesName('-myapp', 'Name', makeGetters(), {});
67
+
68
+ expect(errors).toContain('validation.dns.label.startHyphen:{"key":"Name"}');
69
+ });
70
+
71
+ it('rejects name ending with hyphen', () => {
72
+ const errors = validateKubernetesName('myapp-', 'Name', makeGetters(), {});
73
+
74
+ expect(errors).toContain('validation.dns.label.endHyphen:{"key":"Name"}');
75
+ });
76
+
77
+ it('rejects name starting and ending with hyphen', () => {
78
+ const errors = validateKubernetesName('-myapp-', 'Name', makeGetters(), {});
79
+
80
+ expect(errors).toContain('validation.dns.label.startHyphen:{"key":"Name"}');
81
+ expect(errors).toContain('validation.dns.label.endHyphen:{"key":"Name"}');
82
+ });
83
+
84
+ it('allows hyphen in the middle', () => {
85
+ const errors = validateKubernetesName('my-app', 'Name', makeGetters(), {});
86
+
87
+ expect(errors).toStrictEqual([]);
88
+ });
89
+ });
90
+
91
+ describe('length rules', () => {
92
+ it('rejects empty string', () => {
93
+ const errors = validateKubernetesName('', 'Name', makeGetters(), {});
94
+
95
+ expect(errors).toContain('validation.dns.label.emptyLabel:{"key":"Name","min":1}');
96
+ });
97
+
98
+ it('rejects name exceeding 63 characters', () => {
99
+ const name = 'a'.repeat(64);
100
+ const errors = validateKubernetesName(name, 'Name', makeGetters(), {});
101
+
102
+ expect(errors).toContain('validation.dns.label.tooLongLabel:{"key":"Name","max":63}');
103
+ });
104
+
105
+ it('respects custom maxLength option', () => {
106
+ const errors = validateKubernetesName('ab', 'Name', makeGetters(), { maxLength: 1 });
107
+
108
+ expect(errors).toContain('validation.dns.label.tooLongLabel:{"key":"Name","max":1}');
109
+ });
110
+
111
+ it('respects custom minLength option', () => {
112
+ const errors = validateKubernetesName('ab', 'Name', makeGetters(), { minLength: 5 });
113
+
114
+ expect(errors).toContain('validation.dns.label.emptyLabel:{"key":"Name","min":5}');
115
+ });
116
+ });
117
+
118
+ describe('hostname mode', () => {
119
+ it('uses hostname errorKey when forHostname is true', () => {
120
+ const errors = validateKubernetesName('-myapp', 'Name', makeGetters(), { forHostname: true });
121
+
122
+ expect(errors).toContain('validation.dns.hostname.startHyphen:{"key":"Name"}');
123
+ });
124
+
125
+ it('uses label errorKey by default', () => {
126
+ const errors = validateKubernetesName('-myapp', 'Name', makeGetters(), {});
127
+
128
+ expect(errors).toContain('validation.dns.label.startHyphen:{"key":"Name"}');
129
+ });
130
+ });
131
+
132
+ describe('custom errorKey', () => {
133
+ it('uses custom errorKey when provided', () => {
134
+ const errors = validateKubernetesName('-myapp', 'Name', makeGetters(), { errorKey: 'custom' });
135
+
136
+ expect(errors).toContain('validation.dns.custom.startHyphen:{"key":"Name"}');
137
+ });
138
+ });
139
+
140
+ describe('errors array accumulation', () => {
141
+ it('appends to existing errors array', () => {
142
+ const existing = ['existing-error'];
143
+ const errors = validateKubernetesName('', 'Name', makeGetters(), {}, existing);
144
+
145
+ expect(errors[0]).toBe('existing-error');
146
+ expect(errors.length).toBeGreaterThan(1);
147
+ });
148
+
149
+ it('defaults to empty errors array', () => {
150
+ const errors = validateKubernetesName('valid', 'Name', makeGetters(), {});
151
+
152
+ expect(errors).toStrictEqual([]);
153
+ });
154
+ });
155
+
156
+ describe('invalidChars option', () => {
157
+ it('rejects characters matching invalidChars pattern', () => {
158
+ const errors = validateKubernetesName('my-App', 'Name', makeGetters(), { invalidChars: 'A-Z' });
159
+
160
+ expect(errors.length).toBeGreaterThan(0);
161
+ });
162
+ });
163
+ });
@@ -0,0 +1,246 @@
1
+ import { containerImages } from '@shell/utils/validators/container-images';
2
+ import { flowOutput } from '@shell/utils/validators/flow-output';
3
+ import { logdna } from '@shell/utils/validators/logging-outputs';
4
+ import { matching, interval } from '@shell/utils/validators/monitoring-route';
5
+
6
+ const mockGetters = {
7
+ 'i18n/t': (key: string, args?: object) => (args ? `${ key }:${ JSON.stringify(args) }` : key),
8
+ 'i18n/exists': () => false,
9
+ };
10
+
11
+ describe('validators/container-images', () => {
12
+ describe('containerImages', () => {
13
+ it('adds error when containers list is missing', () => {
14
+ const errors: string[] = [];
15
+ const spec = { template: { spec: {} } };
16
+
17
+ containerImages(spec, mockGetters, errors);
18
+ expect(errors.some((e) => e.includes('validation.required'))).toBe(true);
19
+ });
20
+
21
+ it('adds error when containers list is empty', () => {
22
+ const errors: string[] = [];
23
+ const spec = { template: { spec: { containers: [] } } };
24
+
25
+ containerImages(spec, mockGetters, errors);
26
+ expect(errors.some((e) => e.includes('validation.required'))).toBe(true);
27
+ });
28
+
29
+ it('adds error when a container has no image', () => {
30
+ const errors: string[] = [];
31
+ const spec = { template: { spec: { containers: [{ name: 'web', image: '' }] } } };
32
+
33
+ containerImages(spec, mockGetters, errors);
34
+ expect(errors.some((e) => e.includes('workload.validation.containerImage'))).toBe(true);
35
+ });
36
+
37
+ it('includes container name in error message', () => {
38
+ const errors: string[] = [];
39
+ const spec = { template: { spec: { containers: [{ name: 'my-container', image: '' }] } } };
40
+
41
+ containerImages(spec, mockGetters, errors);
42
+ expect(errors.some((e) => e.includes('my-container'))).toBe(true);
43
+ });
44
+
45
+ it('does not add error when all containers have images', () => {
46
+ const errors: string[] = [];
47
+ const spec = { template: { spec: { containers: [{ name: 'web', image: 'nginx:latest' }] } } };
48
+
49
+ containerImages(spec, mockGetters, errors);
50
+ expect(errors).toStrictEqual([]);
51
+ });
52
+
53
+ it('adds error for each container missing an image', () => {
54
+ const errors: string[] = [];
55
+ const spec = {
56
+ template: {
57
+ spec: {
58
+ containers: [
59
+ { name: 'c1', image: '' },
60
+ { name: 'c2', image: 'nginx' },
61
+ { name: 'c3', image: '' },
62
+ ]
63
+ }
64
+ }
65
+ };
66
+
67
+ containerImages(spec, mockGetters, errors);
68
+ expect(errors.filter((e) => e.includes('workload.validation.containerImage'))).toHaveLength(2);
69
+ });
70
+
71
+ it('reads podSpec from cronjob jobTemplate path', () => {
72
+ const errors: string[] = [];
73
+ const spec = { jobTemplate: { spec: { template: { spec: { containers: [] } } } } };
74
+
75
+ containerImages(spec, mockGetters, errors);
76
+ expect(errors.some((e) => e.includes('validation.required'))).toBe(true);
77
+ });
78
+
79
+ it('does not error for cronjob with valid containers', () => {
80
+ const errors: string[] = [];
81
+ const spec = { jobTemplate: { spec: { template: { spec: { containers: [{ name: 'job', image: 'busybox' }] } } } } };
82
+
83
+ containerImages(spec, mockGetters, errors);
84
+ expect(errors).toStrictEqual([]);
85
+ });
86
+ });
87
+ });
88
+
89
+ describe('validators/flow-output', () => {
90
+ describe('flowOutput', () => {
91
+ it('adds error when verifyLocal and both refs are empty', () => {
92
+ const errors: string[] = [];
93
+
94
+ flowOutput({ localOutputRefs: [], globalOutputRefs: [] }, mockGetters, errors, ['verifyLocal']);
95
+ expect(errors).toStrictEqual(['validation.flowOutput.both']);
96
+ });
97
+
98
+ it('does not add error when verifyLocal and localOutputRefs is populated', () => {
99
+ const errors: string[] = [];
100
+
101
+ flowOutput({ localOutputRefs: ['ref1'], globalOutputRefs: [] }, mockGetters, errors, ['verifyLocal']);
102
+ expect(errors).toStrictEqual([]);
103
+ });
104
+
105
+ it('does not add error when verifyLocal and globalOutputRefs is populated', () => {
106
+ const errors: string[] = [];
107
+
108
+ flowOutput({ localOutputRefs: [], globalOutputRefs: ['ref1'] }, mockGetters, errors, ['verifyLocal']);
109
+ expect(errors).toStrictEqual([]);
110
+ });
111
+
112
+ it('adds error when not verifyLocal and globalOutputRefs is empty', () => {
113
+ const errors: string[] = [];
114
+
115
+ flowOutput({ localOutputRefs: ['local'], globalOutputRefs: [] }, mockGetters, errors, []);
116
+ expect(errors).toStrictEqual(['validation.flowOutput.global']);
117
+ });
118
+
119
+ it('does not add error when not verifyLocal and globalOutputRefs is populated', () => {
120
+ const errors: string[] = [];
121
+
122
+ flowOutput({ localOutputRefs: [], globalOutputRefs: ['global'] }, mockGetters, errors, []);
123
+ expect(errors).toStrictEqual([]);
124
+ });
125
+
126
+ it('uses default empty array when localOutputRefs is missing', () => {
127
+ const errors: string[] = [];
128
+
129
+ flowOutput({ globalOutputRefs: [] }, mockGetters, errors, ['verifyLocal']);
130
+ expect(errors).toStrictEqual(['validation.flowOutput.both']);
131
+ });
132
+ });
133
+ });
134
+
135
+ describe('validators/logging-outputs', () => {
136
+ describe('logdna', () => {
137
+ it('returns without error when value is empty', () => {
138
+ const errors: string[] = [];
139
+
140
+ logdna({}, mockGetters, errors, []);
141
+ expect(errors).toStrictEqual([]);
142
+ });
143
+
144
+ it('returns without error when value is null', () => {
145
+ const errors: string[] = [];
146
+
147
+ logdna(null, mockGetters, errors, []);
148
+ expect(errors).toStrictEqual([]);
149
+ });
150
+
151
+ it('adds error when api_key is missing', () => {
152
+ const errors: string[] = [];
153
+
154
+ logdna({ host: 'example.com' }, mockGetters, errors, []);
155
+ expect(errors).toStrictEqual(['validation.output.logdna.apiKey']);
156
+ });
157
+
158
+ it('adds error when api_key is empty string', () => {
159
+ const errors: string[] = [];
160
+
161
+ logdna({ api_key: '' }, mockGetters, errors, []);
162
+ expect(errors).toStrictEqual(['validation.output.logdna.apiKey']);
163
+ });
164
+
165
+ it('does not add error when api_key is provided', () => {
166
+ const errors: string[] = [];
167
+
168
+ logdna({ api_key: 'my-key-123' }, mockGetters, errors, []);
169
+ expect(errors).toStrictEqual([]);
170
+ });
171
+ });
172
+ });
173
+
174
+ describe('validators/monitoring-route', () => {
175
+ describe('matching', () => {
176
+ it('adds error when both match and match_re are missing', () => {
177
+ const errors: string[] = [];
178
+
179
+ matching({}, mockGetters, errors, []);
180
+ expect(errors).toStrictEqual(['validation.monitoring.route.match']);
181
+ });
182
+
183
+ it('adds error when both match and match_re are empty', () => {
184
+ const errors: string[] = [];
185
+
186
+ matching({ match: {}, match_re: {} }, mockGetters, errors, []);
187
+ expect(errors).toStrictEqual(['validation.monitoring.route.match']);
188
+ });
189
+
190
+ it('does not add error when match has entries', () => {
191
+ const errors: string[] = [];
192
+
193
+ matching({ match: { alertname: 'myAlert' } }, mockGetters, errors, []);
194
+ expect(errors).toStrictEqual([]);
195
+ });
196
+
197
+ it('does not add error when match_re has entries', () => {
198
+ const errors: string[] = [];
199
+
200
+ matching({ match_re: { alertname: '.*' } }, mockGetters, errors, []);
201
+ expect(errors).toStrictEqual([]);
202
+ });
203
+
204
+ it('does not add error when spec is null', () => {
205
+ const errors: string[] = [];
206
+
207
+ matching(null, mockGetters, errors, []);
208
+ expect(errors).toStrictEqual(['validation.monitoring.route.match']);
209
+ });
210
+ });
211
+
212
+ describe('interval', () => {
213
+ it.each([
214
+ ['30s', true],
215
+ ['5m', true],
216
+ ['2h', true],
217
+ ['0s', true],
218
+ ])('accepts valid interval %s', (value, _valid) => {
219
+ const errors: string[] = [];
220
+
221
+ interval(value, mockGetters, errors, [], 'Group Wait');
222
+ expect(errors).toStrictEqual([]);
223
+ });
224
+
225
+ it.each([
226
+ ['30'],
227
+ ['5min'],
228
+ ['2 h'],
229
+ ['abc'],
230
+ [''],
231
+ ['1d'],
232
+ ])('rejects invalid interval %s', (value) => {
233
+ const errors: string[] = [];
234
+
235
+ interval(value, mockGetters, errors, [], 'Group Wait');
236
+ expect(errors.some((e) => e.includes('validation.monitoring.route.interval'))).toBe(true);
237
+ });
238
+
239
+ it('includes displayKey in error message', () => {
240
+ const errors: string[] = [];
241
+
242
+ interval('invalid', mockGetters, errors, [], 'RepeatInterval');
243
+ expect(errors.some((e) => e.includes('RepeatInterval'))).toBe(true);
244
+ });
245
+ });
246
+ });