@rancher/shell 3.0.5-rc.6 → 3.0.5-rc.8

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 (243) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +16 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +18 -12
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/themes/_dark.scss +262 -258
  9. package/assets/styles/themes/_light.scss +538 -509
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +110 -29
  12. package/chart/__tests__/S3.test.ts +2 -1
  13. package/cloud-credential/generic.vue +18 -10
  14. package/cloud-credential/harvester.vue +1 -9
  15. package/components/AdvancedSection.vue +8 -0
  16. package/components/ChartReadme.vue +17 -7
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/Drawer/Chrome.vue +0 -1
  19. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
  20. package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
  21. package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
  22. package/components/InstallHelmCharts.vue +656 -0
  23. package/components/LazyImage.vue +60 -4
  24. package/components/Loading.vue +1 -1
  25. package/components/LocaleSelector.vue +7 -2
  26. package/components/Markdown.vue +4 -0
  27. package/components/PaginatedResourceTable.vue +46 -1
  28. package/components/PromptRestore.vue +22 -44
  29. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  30. package/components/Resource/Detail/Masthead/index.vue +37 -0
  31. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
  32. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
  33. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
  34. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
  35. package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
  36. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  37. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  38. package/components/Resource/Detail/Metadata/index.vue +17 -2
  39. package/components/Resource/Detail/Page.vue +35 -21
  40. package/components/Resource/Detail/SpacedRow.vue +1 -1
  41. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  42. package/components/Resource/Detail/TitleBar/composables.ts +5 -5
  43. package/components/Resource/Detail/TitleBar/index.vue +12 -3
  44. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  45. package/components/ResourceDetail/index.vue +569 -72
  46. package/components/ResourceList/index.vue +1 -0
  47. package/components/ResourceTable.vue +6 -1
  48. package/components/ResourceYaml.vue +1 -1
  49. package/components/RichTranslation.vue +106 -0
  50. package/components/SlideInPanelManager.vue +13 -10
  51. package/components/SortableTable/index.vue +5 -5
  52. package/components/SortableTable/selection.js +0 -1
  53. package/components/Tabbed/index.vue +35 -4
  54. package/components/__tests__/LazyImage.spec.ts +121 -0
  55. package/components/__tests__/PromptRestore.test.ts +1 -65
  56. package/components/__tests__/RichTranslation.test.ts +115 -0
  57. package/components/fleet/FleetStatus.vue +4 -0
  58. package/components/fleet/dashboard/ResourcePanel.vue +2 -1
  59. package/components/form/ClusterAppearance.vue +5 -0
  60. package/components/form/FileImageSelector.vue +1 -1
  61. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  62. package/components/form/NameNsDescription.vue +1 -0
  63. package/components/form/Networking.vue +24 -19
  64. package/components/form/ProjectMemberEditor.vue +1 -1
  65. package/components/form/ResourceLabeledSelect.vue +22 -8
  66. package/components/form/ResourceTabs/index.vue +20 -0
  67. package/components/form/SecretSelector.vue +9 -0
  68. package/components/form/SelectOrCreateAuthSecret.vue +6 -3
  69. package/components/form/__tests__/Networking.test.ts +116 -0
  70. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  71. package/components/formatter/FleetApplicationSource.vue +25 -17
  72. package/components/formatter/PodImages.vue +1 -1
  73. package/components/formatter/__tests__/LiveDate.test.ts +10 -2
  74. package/components/google/AccountAccess.vue +44 -46
  75. package/components/nav/Favorite.vue +4 -0
  76. package/components/nav/Group.vue +4 -1
  77. package/components/nav/NotificationCenter/Notification.vue +1 -27
  78. package/components/nav/WindowManager/index.vue +3 -3
  79. package/composables/resources.ts +2 -2
  80. package/config/labels-annotations.js +3 -2
  81. package/config/pagination-table-headers.js +8 -1
  82. package/config/product/explorer.js +27 -2
  83. package/config/product/manager.js +0 -1
  84. package/config/query-params.js +10 -0
  85. package/config/router/routes.js +21 -1
  86. package/config/system-namespaces.js +1 -1
  87. package/config/table-headers.js +30 -1
  88. package/config/types.js +1 -1
  89. package/config/version.js +1 -1
  90. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  91. package/detail/__tests__/workload.test.ts +164 -0
  92. package/detail/configmap.vue +33 -75
  93. package/detail/projectsecret.vue +11 -0
  94. package/detail/provisioning.cattle.io.cluster.vue +351 -369
  95. package/detail/secret.vue +49 -308
  96. package/detail/workload/index.vue +38 -21
  97. package/dialog/InstallExtensionDialog.vue +8 -5
  98. package/dialog/RotateEncryptionKeyDialog.vue +10 -30
  99. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  100. package/edit/auth/ldap/__tests__/config.test.ts +14 -0
  101. package/edit/auth/ldap/config.vue +24 -0
  102. package/edit/compliance.cattle.io.clusterscan.vue +1 -1
  103. package/edit/configmap.vue +4 -1
  104. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  105. package/edit/fleet.cattle.io.helmop.vue +78 -56
  106. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  107. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  108. package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
  109. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  110. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  111. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  112. package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
  113. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  114. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  115. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  116. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
  117. package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
  118. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  119. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
  120. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  121. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  122. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  123. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  124. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
  125. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  126. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  127. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  128. package/edit/secret/basic.vue +1 -0
  129. package/edit/secret/index.vue +126 -15
  130. package/edit/workload/index.vue +5 -14
  131. package/list/projectsecret.vue +345 -0
  132. package/list/provisioning.cattle.io.cluster.vue +1 -69
  133. package/list/secret.vue +109 -0
  134. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  135. package/machine-config/google.vue +9 -1
  136. package/machine-config/vmwarevsphere.vue +7 -17
  137. package/mixins/__tests__/brand.spec.ts +2 -2
  138. package/mixins/chart.js +0 -2
  139. package/mixins/create-edit-view/impl.js +10 -1
  140. package/mixins/resource-fetch-api-pagination.js +11 -12
  141. package/mixins/resource-fetch.js +3 -1
  142. package/models/__tests__/chart.test.ts +111 -80
  143. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  144. package/models/__tests__/node.test.ts +7 -63
  145. package/models/catalog.cattle.io.app.js +1 -1
  146. package/models/catalog.cattle.io.operation.js +1 -1
  147. package/models/chart.js +36 -20
  148. package/models/cloudcredential.js +2 -163
  149. package/models/cluster/node.js +7 -7
  150. package/models/cluster.x-k8s.io.machine.js +3 -3
  151. package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
  152. package/models/compliance.cattle.io.clusterscan.js +2 -2
  153. package/models/configmap.js +4 -0
  154. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  155. package/models/fleet-application.js +0 -17
  156. package/models/fleet.cattle.io.cluster.js +2 -2
  157. package/models/fleet.cattle.io.gitrepo.js +15 -1
  158. package/models/fleet.cattle.io.helmop.js +26 -22
  159. package/models/management.cattle.io.setting.js +4 -0
  160. package/models/persistentvolumeclaim.js +1 -1
  161. package/models/pod.js +2 -2
  162. package/models/provisioning.cattle.io.cluster.js +39 -67
  163. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  164. package/models/secret.js +161 -2
  165. package/models/storage.k8s.io.storageclass.js +2 -2
  166. package/models/workload.js +3 -3
  167. package/package.json +11 -10
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  172. package/pages/c/_cluster/apps/charts/index.vue +46 -35
  173. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  174. package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
  175. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  176. package/pages/c/_cluster/fleet/index.vue +103 -45
  177. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  178. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  179. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  180. package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
  181. package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
  182. package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
  183. package/plugins/dashboard-store/actions.js +42 -22
  184. package/plugins/dashboard-store/normalize.js +29 -17
  185. package/plugins/dashboard-store/resource-class.js +83 -17
  186. package/plugins/steve/__tests__/getters.test.ts +1 -1
  187. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  188. package/plugins/steve/getters.js +8 -2
  189. package/plugins/steve/resourceWatcher.js +10 -3
  190. package/plugins/steve/steve-pagination-utils.ts +14 -3
  191. package/plugins/steve/subscribe.js +192 -19
  192. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  193. package/rancher-components/Card/Card.vue +0 -18
  194. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  195. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  196. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  197. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  198. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  199. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  200. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  201. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  202. package/rancher-components/Pill/types.ts +2 -0
  203. package/rancher-components/RcButton/RcButton.vue +1 -1
  204. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  205. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  206. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  207. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  208. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  209. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  210. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  213. package/store/__tests__/catalog.test.ts +93 -1
  214. package/store/aws.js +19 -8
  215. package/store/catalog.js +8 -3
  216. package/types/kube/kube-api.ts +12 -0
  217. package/types/resources/settings.d.ts +1 -1
  218. package/types/shell/index.d.ts +643 -585
  219. package/types/store/pagination.types.ts +16 -6
  220. package/types/uiplugins.ts +73 -0
  221. package/utils/__tests__/back-off.test.ts +354 -0
  222. package/utils/__tests__/create-yaml.test.ts +235 -0
  223. package/utils/__tests__/kontainer.test.ts +19 -0
  224. package/utils/__tests__/uiplugins.test.ts +84 -0
  225. package/utils/back-off.ts +176 -0
  226. package/utils/create-yaml.js +103 -9
  227. package/utils/dynamic-importer.js +8 -0
  228. package/utils/kontainer.ts +3 -5
  229. package/utils/pagination-utils.ts +18 -0
  230. package/utils/style.ts +3 -0
  231. package/utils/uiplugins.ts +29 -2
  232. package/utils/validators/__tests__/setting.test.js +92 -0
  233. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  234. package/utils/validators/formRules/index.ts +83 -8
  235. package/utils/validators/setting.js +17 -0
  236. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  237. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  238. package/components/ResourceDetail/legacy.vue +0 -562
  239. package/components/formatter/CloudCredExpired.vue +0 -69
  240. package/models/etcdbackup.js +0 -45
  241. package/pages/explorer/resource/detail/configmap.vue +0 -42
  242. package/pages/explorer/resource/detail/secret.vue +0 -50
  243. package/utils/aws.js +0 -0
@@ -64,12 +64,14 @@ export class PaginationFilterField {
64
64
  field?: string;
65
65
  /**
66
66
  * Value of field within the object to filter by for example the y of x=y
67
+ *
68
+ * This can be empty if `exists` is true
67
69
  */
68
- value: string;
70
+ value?: string;
69
71
  /**
70
72
  * Equality field within the object to filter by for example the `=` or `!=` of x=y
71
73
  */
72
- equals: boolean;
74
+ equals?: boolean;
73
75
  /**
74
76
  * Match the field exactly. False for partial matches
75
77
  *
@@ -77,18 +79,26 @@ export class PaginationFilterField {
77
79
  * Exact: true. "p" no, "pod", no, "pod1" yes
78
80
  * Exact: false. "p" yes, "pod", yes, "pod1" yes
79
81
  */
80
- exact: boolean;
82
+ exact?: boolean;
83
+
84
+ /**
85
+ * Check if the field/property exists, regardless of value
86
+ *
87
+ * If this is false it does not flip the expectation, just doesn't add the field
88
+ */
89
+ exists?: boolean;
81
90
 
82
91
  constructor(
83
92
  {
84
- field, value, equals = true, exact = true
93
+ field, value = '', equals = true, exact = true, exists = false
85
94
  }:
86
- { field?: string; value: string; equals?: boolean; exact?: boolean;}
95
+ { field?: string; value?: string; equals?: boolean; exact?: boolean; exists?:boolean;}
87
96
  ) {
88
97
  this.field = field;
89
98
  this.value = value;
90
99
  this.equals = equals;
91
100
  this.exact = exact;
101
+ this.exists = exists;
92
102
  }
93
103
  }
94
104
 
@@ -228,7 +238,7 @@ export class PaginationParamFilter extends PaginationParam {
228
238
  /**
229
239
  * Convenience method when you just want an instance of {@link PaginationParamFilter} with a simple `filter=x=y` param
230
240
  */
231
- static createSingleField(field: { field?: string; value: string; equals?: boolean; exact?: boolean }): PaginationParam {
241
+ static createSingleField(field: { field?: string; value?: string; equals?: boolean; exact?: boolean, exists?: boolean }): PaginationParam {
232
242
  return new PaginationParamFilter({ fields: [new PaginationFilterField(field)] });
233
243
  }
234
244
 
@@ -0,0 +1,73 @@
1
+ export type VersionCompatibility = {
2
+ isVersionCompatible: boolean;
3
+ versionIncompatibilityData: any;
4
+ };
5
+
6
+ export type ChartVersion = {
7
+ name: string;
8
+ version: string;
9
+ description: string;
10
+ icon: string;
11
+ apiVersion: string;
12
+ appVersion: string;
13
+ annotations: {
14
+ [key: string]: string;
15
+ };
16
+ type: string;
17
+ urls: string[];
18
+ created: string;
19
+ digest: string;
20
+ key: string;
21
+ repoType: string;
22
+ repoName: string;
23
+ };
24
+
25
+ export type Version = ChartVersion & VersionCompatibility;
26
+
27
+ export type Chart = {
28
+ key: string;
29
+ type: string;
30
+ id: string;
31
+ certified: string;
32
+ sideLabel?: string;
33
+ repoType: string;
34
+ repoName: string;
35
+ repoNameDisplay: string;
36
+ certifiedSort: number;
37
+ icon: string;
38
+ color?: string;
39
+ chartType: string;
40
+ chartName: string;
41
+ chartNameDisplay: string;
42
+ chartDescription: string;
43
+ repoKey: string;
44
+ versions: ChartVersion[];
45
+ categories: any[];
46
+ deprecated: boolean;
47
+ experimental: boolean;
48
+ hidden: boolean;
49
+ targetNamespace: string;
50
+ scope: string;
51
+ provides: any[];
52
+ windowsIncompatible: boolean;
53
+ deploysOnWindows: boolean;
54
+ };
55
+
56
+ export type Plugin = {
57
+ name: string;
58
+ label: string;
59
+ description: string;
60
+ id: string;
61
+ versions: Version[];
62
+ installed: boolean;
63
+ builtin: boolean;
64
+ experimental: boolean;
65
+ certified: boolean;
66
+ chart: Chart;
67
+ incompatibilityMessage: string;
68
+ installableVersions: ChartVersion[];
69
+ displayVersion: string;
70
+ pluginVersionLabel: string;
71
+ icon?: string;
72
+ helmError: boolean;
73
+ };
@@ -0,0 +1,354 @@
1
+ // Import the BackOff instance from your code.
2
+ // Assuming the file is named `backoff.ts` and the default export is the BackOff instance.
3
+ import backOff from '../back-off';
4
+
5
+ describe('backOff', () => {
6
+ // Use Jest's fake timers to control `setTimeout`. This is crucial for testing delays.
7
+ jest.useFakeTimers();
8
+
9
+ // Mock console.log to prevent test logs from cluttering the console output.
10
+ let consoleLogMock: jest.SpyInstance;
11
+ let consoleErrorMock: jest.SpyInstance;
12
+
13
+ beforeEach(() => {
14
+ // Before each test, reset the BackOff state.
15
+ backOff.resetAll();
16
+
17
+ // Create new mocks for each test to ensure a clean slate.
18
+ consoleLogMock = jest.spyOn(console, 'log').mockImplementation(() => {});
19
+ consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {});
20
+ });
21
+
22
+ afterEach(() => {
23
+ // Restore the original console functions after each test.
24
+ consoleLogMock.mockRestore();
25
+ consoleErrorMock.mockRestore();
26
+ });
27
+
28
+ // --- Test Suite for `execute` method ---
29
+
30
+ it('should execute the function immediately the first time without a delay', async() => {
31
+ const delayedFn = jest.fn();
32
+
33
+ // Call the function for the first time.
34
+ await backOff.execute({
35
+ id: 'test1', description: 'Test 1', delayedFn
36
+ });
37
+
38
+ // The function should have been called immediately, since the fake timer hasn't advanced.
39
+ expect(delayedFn).toHaveBeenCalledTimes(0);
40
+ expect(backOff.getBackOff('test1')).toBeDefined();
41
+ });
42
+
43
+ it('should back off and delay the second execution', async() => {
44
+ const delayedFn = jest.fn();
45
+ const id = 'backoff-test';
46
+
47
+ // First call, which should run immediately.
48
+ await backOff.execute({
49
+ id, description: 'Backoff Test', delayedFn,
50
+ });
51
+
52
+ jest.advanceTimersByTime(1);
53
+
54
+ // Expect the first call to be immediate.
55
+ expect(delayedFn).toHaveBeenCalledTimes(1);
56
+
57
+ // Call it a second time. This should initiate a backoff delay.
58
+ await backOff.execute({
59
+ id, description: 'Backoff Test', delayedFn
60
+ });
61
+
62
+ // The function should not have been called a second time yet.
63
+ expect(delayedFn).toHaveBeenCalledTimes(1);
64
+
65
+ // Advance the timer by less than the first backoff delay (250ms).
66
+ jest.advanceTimersByTime(200);
67
+ expect(delayedFn).toHaveBeenCalledTimes(1);
68
+
69
+ // Now, advance the timer by the required delay to trigger the second call.
70
+ jest.advanceTimersByTime(50);
71
+
72
+ // The function should have been called a second time.
73
+ await Promise.resolve();
74
+ expect(delayedFn).toHaveBeenCalledTimes(2);
75
+
76
+ // Verify the backoff entry was created correctly.
77
+ const backOffEntry = backOff.getBackOff(id);
78
+
79
+ expect(backOffEntry.try).toBe(2);
80
+ });
81
+
82
+ it('should implement exponential backoff on subsequent calls', async() => {
83
+ const delayedFn = jest.fn();
84
+ const id = 'exp-backoff';
85
+
86
+ // First call (immediate)
87
+ await backOff.execute({
88
+ id, description: 'Exponential Backoff Test', delayedFn
89
+ });
90
+
91
+ jest.advanceTimersByTime(1);
92
+ expect(delayedFn).toHaveBeenCalledTimes(1);
93
+
94
+ // Second call (should have a delay of 1^2 * 250 = 250ms)
95
+ await backOff.execute({
96
+ id, description: 'Exponential Backoff Test', delayedFn
97
+ });
98
+ jest.advanceTimersByTime(250);
99
+ await Promise.resolve();
100
+ expect(delayedFn).toHaveBeenCalledTimes(2);
101
+
102
+ // Third call (should have a delay of 2^2 * 250 = 1000ms)
103
+ await backOff.execute({
104
+ id, description: 'Exponential Backoff Test', delayedFn
105
+ });
106
+ jest.advanceTimersByTime(1000);
107
+ await Promise.resolve();
108
+ expect(delayedFn).toHaveBeenCalledTimes(3);
109
+
110
+ // Fourth call (should have a delay of 3^2 * 250 = 2250ms)
111
+ await backOff.execute({
112
+ id, description: 'Exponential Backoff Test', delayedFn
113
+ });
114
+ jest.advanceTimersByTime(2250);
115
+ await Promise.resolve();
116
+ expect(delayedFn).toHaveBeenCalledTimes(4);
117
+ });
118
+
119
+ it('should skip execution if a previous backoff process is already running', async() => {
120
+ const delayedFn = jest.fn();
121
+ const id = 'skip-test';
122
+
123
+ await backOff.execute({
124
+ id, description: 'Skip Test', delayedFn
125
+ });
126
+ expect(delayedFn).toHaveBeenCalledTimes(0);
127
+
128
+ // Second call, will be ignored
129
+ await backOff.execute({
130
+ id, description: 'Skip Test', delayedFn
131
+ });
132
+
133
+ expect(delayedFn).toHaveBeenCalledTimes(0);
134
+
135
+ // We should only have 1 call so far.
136
+ jest.advanceTimersByTime(1);
137
+ await Promise.resolve();
138
+ expect(delayedFn).toHaveBeenCalledTimes(1);
139
+
140
+ // A third call while the first is still pending.
141
+ // This call should be ignored and the delayedFn should not be executed.
142
+ await backOff.execute({
143
+ id, description: 'Skip Test', delayedFn
144
+ });
145
+
146
+ expect(delayedFn).toHaveBeenCalledTimes(1);
147
+
148
+ jest.advanceTimersByTime(300);
149
+ await Promise.resolve();
150
+ expect(delayedFn).toHaveBeenCalledTimes(2);
151
+
152
+ // Advance timers to complete the pending second call.
153
+ jest.advanceTimersByTime(1000);
154
+ await Promise.resolve();
155
+ // Now there should be 2 calls, not 3.
156
+ expect(delayedFn).toHaveBeenCalledTimes(2);
157
+ });
158
+
159
+ it('should not execute if the number of retries is exceeded', async() => {
160
+ const delayedFn = jest.fn();
161
+ const id = 'retries-test';
162
+
163
+ // Set retries to 2.
164
+ const retries = 2;
165
+
166
+ // Call 1 (immediate)
167
+ await backOff.execute({
168
+ id, description: 'Retries Test', retries, delayedFn
169
+ });
170
+ jest.advanceTimersByTime(1);
171
+ await Promise.resolve();
172
+ expect(delayedFn).toHaveBeenCalledTimes(1);
173
+
174
+ // Call 2 (after 250ms delay)
175
+ await backOff.execute({
176
+ id, description: 'Retries Test', retries, delayedFn
177
+ });
178
+ jest.advanceTimersByTime(250);
179
+ await Promise.resolve();
180
+ expect(delayedFn).toHaveBeenCalledTimes(2);
181
+
182
+ // Call 3 (should be ignored because it exceeds the `retries` limit of 2)
183
+ await backOff.execute({
184
+ id, description: 'Retries Test', retries, delayedFn
185
+ });
186
+ jest.advanceTimersByTime(250);
187
+ await Promise.resolve();
188
+
189
+ expect(delayedFn).toHaveBeenCalledTimes(2);
190
+ });
191
+
192
+ it('should skip execution if `canFn` returns false', async() => {
193
+ const delayedFn = jest.fn();
194
+ const canFn = jest.fn().mockResolvedValue(false);
195
+
196
+ await backOff.execute({
197
+ id: 'canfn-test', description: 'canFn Test', canFn, delayedFn
198
+ });
199
+
200
+ jest.advanceTimersByTime(250);
201
+ await Promise.resolve();
202
+
203
+ expect(delayedFn).not.toHaveBeenCalled();
204
+ });
205
+
206
+ it('should not clear backoff entry if the delayedFn throws an error', async() => {
207
+ const id = 'error-test';
208
+ const delayedFn = jest.fn().mockRejectedValue(new Error('Test Error'));
209
+
210
+ // Call the function for the first time.
211
+ await backOff.execute({
212
+ id, description: 'Error Test', delayedFn
213
+ });
214
+
215
+ // Wait for the immediate call to finish.
216
+ jest.advanceTimersByTime(1);
217
+ await Promise.resolve();
218
+ await Promise.resolve();
219
+ await Promise.resolve();
220
+
221
+ // The entry should be removed after success/failure.
222
+ expect(backOff.getBackOff(id).timeoutId).toBeUndefined();
223
+
224
+ // Call again to trigger a backoff delay and an error.
225
+ await backOff.execute({
226
+ id, description: 'Error Test', delayedFn
227
+ });
228
+
229
+ // Advance timers to trigger the delayed function.
230
+ jest.advanceTimersByTime(250);
231
+ await Promise.resolve();
232
+ await Promise.resolve();
233
+ await Promise.resolve();
234
+
235
+ // The timeoutId should be cleared, but the `try` count should be preserved on the next call.
236
+ expect(backOff.getBackOff(id).timeoutId).toBeUndefined();
237
+
238
+ // Check if the next call will still back off.
239
+ const newDelayedFn = jest.fn();
240
+
241
+ await backOff.execute({
242
+ id, description: 'Error Test', delayedFn: newDelayedFn
243
+ });
244
+ expect(newDelayedFn).not.toHaveBeenCalled(); // The next call should still be delayed
245
+ });
246
+
247
+ it('should save metadata', async() => {
248
+ const delayedFn = jest.fn();
249
+ const id = 'exp-backoff';
250
+ const metadata = { a: true };
251
+
252
+ // First call (immediate)
253
+ await backOff.execute({
254
+ id, description: 'Exponential Backoff Test', delayedFn, metadata
255
+ });
256
+
257
+ expect(backOff.getBackOff(id)).toBeDefined();
258
+ expect(backOff.getBackOff(id).metadata).toStrictEqual(metadata);
259
+ });
260
+
261
+ // --- Test Suite for Reset methods ---
262
+
263
+ it('should reset a specific backoff process', async() => {
264
+ const delayedFn = jest.fn();
265
+ const id = 'reset-test';
266
+
267
+ // Start a backoff process.
268
+ await backOff.execute({
269
+ id, description: 'Reset Test', delayedFn
270
+ });
271
+
272
+ expect(backOff.getBackOff(id)).toBeDefined();
273
+
274
+ // Reset the process.
275
+ backOff.reset(id);
276
+
277
+ // The entry should be deleted from the map.
278
+ expect(backOff.getBackOff(id)).toBeUndefined();
279
+
280
+ // Now, a new execution should not be delayed.
281
+ await backOff.execute({
282
+ id, description: 'Reset Test', delayedFn
283
+ });
284
+
285
+ jest.advanceTimersByTime(250);
286
+ await Promise.resolve();
287
+ expect(delayedFn).toHaveBeenCalledTimes(1);
288
+ });
289
+
290
+ it('should reset all backoff processes', async() => {
291
+ const delayedFn1 = jest.fn();
292
+ const delayedFn2 = jest.fn();
293
+
294
+ await backOff.execute({
295
+ id: 'all-1', description: 'All 1', delayedFn: delayedFn1
296
+ });
297
+ await backOff.execute({
298
+ id: 'all-1', description: 'All 1', delayedFn: delayedFn1
299
+ });
300
+
301
+ await backOff.execute({
302
+ id: 'all-2', description: 'All 2', delayedFn: delayedFn2
303
+ });
304
+ await backOff.execute({
305
+ id: 'all-2', description: 'All 2', delayedFn: delayedFn2
306
+ });
307
+
308
+ expect(backOff.getBackOff('all-1')).toBeDefined();
309
+ expect(backOff.getBackOff('all-2')).toBeDefined();
310
+
311
+ // Reset all processes.
312
+ backOff.resetAll();
313
+
314
+ expect(backOff.getBackOff('all-1')).toBeUndefined();
315
+ expect(backOff.getBackOff('all-2')).toBeUndefined();
316
+ });
317
+
318
+ it('should reset only processes with a specific prefix', async() => {
319
+ const delayedFn = jest.fn();
320
+
321
+ await backOff.execute({
322
+ id: 'prefix-test-1', description: 'Prefix Test 1', delayedFn
323
+ });
324
+ await backOff.execute({
325
+ id: 'prefix-test-1', description: 'Prefix Test 1', delayedFn
326
+ });
327
+
328
+ await backOff.execute({
329
+ id: 'prefix-test-2', description: 'Prefix Test 2', delayedFn
330
+ });
331
+ await backOff.execute({
332
+ id: 'prefix-test-2', description: 'Prefix Test 2', delayedFn
333
+ });
334
+
335
+ await backOff.execute({
336
+ id: 'other-test-1', description: 'Other Test 1', delayedFn
337
+ });
338
+ await backOff.execute({
339
+ id: 'other-test-1', description: 'Other Test 1', delayedFn
340
+ });
341
+
342
+ expect(backOff.getBackOff('prefix-test-1')).toBeDefined();
343
+ expect(backOff.getBackOff('prefix-test-2')).toBeDefined();
344
+ expect(backOff.getBackOff('other-test-1')).toBeDefined();
345
+
346
+ // Reset only the "prefix-test" processes.
347
+ backOff.resetPrefix('prefix-test');
348
+
349
+ expect(backOff.getBackOff('prefix-test-1')).toBeUndefined();
350
+ expect(backOff.getBackOff('prefix-test-2')).toBeUndefined();
351
+ // The other process should still exist.
352
+ expect(backOff.getBackOff('other-test-1')).toBeDefined();
353
+ });
354
+ });