@rancher/shell 3.0.8 → 3.0.9-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apis/intf/modal.ts +38 -0
- package/apis/intf/slide-in.ts +3 -1
- package/apis/shell/__tests__/slide-in.test.ts +36 -0
- package/apis/shell/slide-in.ts +5 -1
- package/assets/styles/base/_color.scss +1 -0
- package/assets/styles/base/_typography.scss +14 -5
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +1 -1
- package/assets/translations/en-us.yaml +94 -33
- package/assets/translations/zh-hans.yaml +0 -2
- package/components/ActionMenuShell.vue +4 -4
- package/components/CodeMirror.vue +4 -3
- package/components/DetailText.vue +54 -7
- package/components/Drawer/Chrome.vue +11 -4
- package/components/Drawer/DrawerCard.vue +19 -0
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
- package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
- package/components/Drawer/types.ts +1 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
- package/components/LocaleSelector.vue +1 -1
- package/components/Markdown.vue +1 -1
- package/components/PopoverCard.vue +3 -3
- package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
- package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
- package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
- package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
- package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
- package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
- package/components/Resource/Detail/Cards.vue +27 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
- package/components/Resource/Detail/Masthead/index.vue +5 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
- package/components/Resource/Detail/ResourceRow.types.ts +14 -0
- package/components/Resource/Detail/ResourceRow.vue +23 -35
- package/components/Resource/Detail/StatusRow.vue +5 -2
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +41 -6
- package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
- package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
- package/components/ResourceDetail/Masthead/index.vue +1 -0
- package/components/ResourceDetail/Masthead/latest.vue +8 -1
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/Setting.vue +1 -1
- package/components/SortableTable/index.vue +25 -0
- package/components/SortableTable/selection.js +25 -12
- package/components/SortableTable/sorting.js +1 -1
- package/components/Tabbed/Tab.vue +1 -0
- package/components/Tabbed/index.vue +29 -6
- package/components/Window/ContainerShell.vue +10 -13
- package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
- package/components/fleet/FleetClusterTargets/index.vue +82 -29
- package/components/fleet/FleetClusters.vue +26 -12
- package/components/fleet/FleetGitRepoPaths.vue +2 -2
- package/components/fleet/FleetResources.vue +14 -0
- package/components/fleet/FleetValuesFrom.vue +2 -2
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
- package/components/fleet/dashboard/ResourceDetails.vue +96 -123
- package/components/form/Conditions.vue +1 -15
- package/components/form/HookOption.vue +5 -0
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/LifecycleHooks.vue +2 -6
- package/components/form/ResourceLabeledSelect.vue +12 -1
- package/components/form/SeccompProfile.vue +113 -0
- package/components/form/Security.vue +244 -133
- package/components/form/__tests__/LabeledSelect.test.ts +1 -1
- package/components/form/__tests__/SeccompProfile.test.js +124 -0
- package/components/form/__tests__/Security.test.ts +125 -37
- package/components/formatter/Autoscaler.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +4 -1
- package/components/nav/Group.vue +5 -0
- package/components/nav/Header.vue +3 -3
- package/components/nav/HeaderPageActionMenu.vue +1 -1
- package/components/nav/NamespaceFilter.vue +6 -6
- package/components/nav/NotificationCenter/index.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +41 -16
- package/components/nav/TopLevelMenu.vue +45 -25
- package/components/nav/WorkspaceSwitcher.vue +1 -1
- package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
- package/components/templates/default.vue +0 -3
- package/components/templates/home.vue +0 -3
- package/components/templates/plain.vue +0 -3
- package/composables/useClickOutside.ts +1 -1
- package/config/product/explorer.js +1 -2
- package/config/types.js +41 -8
- package/detail/__tests__/workload.test.ts +8 -16
- package/detail/catalog.cattle.io.app.vue +6 -0
- package/detail/fleet.cattle.io.cluster.vue +6 -0
- package/detail/workload/index.vue +7 -109
- package/edit/__tests__/projectsecret.test.ts +42 -0
- package/edit/auth/__tests__/oidc.test.ts +50 -0
- package/edit/auth/oidc.vue +68 -44
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
- package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
- package/edit/projectsecret.vue +29 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
- package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
- package/edit/workload/__tests__/index.test.ts +122 -85
- package/edit/workload/index.vue +48 -29
- package/edit/workload/mixins/workload.js +85 -32
- package/list/catalog.cattle.io.clusterrepo.vue +1 -1
- package/list/projectsecret.vue +2 -2
- package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
- package/machine-config/amazonec2.vue +2 -2
- package/machine-config/vmwarevsphere.vue +58 -4
- package/mixins/__tests__/brand.spec.ts +18 -13
- package/mixins/__tests__/chart.test.ts +63 -0
- package/mixins/chart.js +56 -51
- package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
- package/models/__tests__/workload.test.ts +333 -0
- package/models/catalog.cattle.io.app.js +8 -0
- package/models/pod.js +14 -0
- package/models/secret.js +1 -1
- package/models/workload.js +93 -27
- package/package.json +4 -4
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
- package/pages/c/_cluster/apps/charts/install.vue +4 -4
- package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
- package/pages/c/_cluster/fleet/index.vue +18 -12
- package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
- package/plugins/dashboard-store/actions.js +9 -8
- package/plugins/dashboard-store/resource-class.js +97 -1
- package/plugins/steve/__tests__/revision.test.ts +84 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
- package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
- package/plugins/steve/mutations.js +9 -0
- package/plugins/steve/revision.ts +26 -0
- package/plugins/steve/steve-pagination-utils.ts +6 -5
- package/plugins/steve/subscribe.js +211 -51
- package/plugins/subscribe-events.ts +2 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
- package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
- package/rancher-components/Pill/index.ts +4 -0
- package/rancher-components/RcButton/RcButton.test.ts +53 -9
- package/rancher-components/RcButton/RcButton.vue +217 -25
- package/rancher-components/RcButton/types.ts +27 -1
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
- package/rancher-components/RcDropdown/types.ts +3 -3
- package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
- package/rancher-components/RcIcon/RcIcon.vue +9 -6
- package/rancher-components/RcIcon/types.ts +13 -9
- package/rancher-components/utils/status.test.ts +10 -15
- package/rancher-components/utils/status.ts +5 -6
- package/store/aws.js +18 -12
- package/store/index.js +4 -8
- package/store/type-map.utils.ts +1 -1
- package/types/kube/kube-api.ts +29 -3
- package/types/rancher/steve.api.ts +40 -0
- package/types/shell/index.d.ts +99 -0
- package/types/store/dashboard-store.types.ts +29 -7
- package/types/store/pagination.types.ts +1 -0
- package/types/store/subscribe-events.types.ts +1 -0
- package/utils/__tests__/azure.test.ts +56 -0
- package/utils/__tests__/back-off.test.ts +364 -245
- package/utils/__tests__/error.test.ts +44 -0
- package/utils/__tests__/fleet.test.ts +8 -1
- package/utils/__tests__/pagination-wrapper.test.ts +167 -0
- package/utils/__tests__/version.test.ts +55 -1
- package/utils/azure.js +12 -0
- package/utils/back-off.ts +302 -69
- package/utils/cspAdaptor.ts +32 -14
- package/utils/dynamic-content/__tests__/index.test.ts +1 -1
- package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
- package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
- package/utils/dynamic-content/index.ts +1 -6
- package/utils/dynamic-content/new-release.ts +5 -3
- package/utils/dynamic-content/types.d.ts +0 -1
- package/utils/error.js +9 -0
- package/utils/fleet.ts +2 -2
- package/utils/inactivity.ts +2 -3
- package/utils/pagination-wrapper.ts +101 -17
- package/utils/validators/formRules/index.ts +3 -0
- package/utils/version.js +38 -0
- package/components/auth/AzureWarning.vue +0 -77
- /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
- /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
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
1
|
import backOff from '../back-off';
|
|
4
2
|
|
|
5
3
|
describe('backOff', () => {
|
|
@@ -10,345 +8,466 @@ describe('backOff', () => {
|
|
|
10
8
|
let consoleLogMock: jest.SpyInstance;
|
|
11
9
|
let consoleErrorMock: jest.SpyInstance;
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
const beforeEachFn = () => {
|
|
14
12
|
// Before each test, reset the BackOff state.
|
|
15
13
|
backOff.resetAll();
|
|
16
14
|
|
|
17
15
|
// Create new mocks for each test to ensure a clean slate.
|
|
18
16
|
consoleLogMock = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
19
17
|
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
20
|
-
}
|
|
18
|
+
};
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
const afterEachFn = () => {
|
|
23
21
|
// Restore the original console functions after each test.
|
|
24
22
|
consoleLogMock.mockRestore();
|
|
25
23
|
consoleErrorMock.mockRestore();
|
|
26
|
-
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('execute', () => {
|
|
27
|
+
beforeEach(beforeEachFn);
|
|
28
|
+
|
|
29
|
+
afterEach(afterEachFn);
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
it('should execute the function immediately the first time without a delay', async() => {
|
|
32
|
+
const delayedFn = jest.fn();
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
// Call the function for the first time.
|
|
35
|
+
await backOff.execute({
|
|
36
|
+
id: 'test1', description: 'Test 1', delayedFn
|
|
37
|
+
});
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
// The function should have been called immediately, since the fake timer hasn't advanced.
|
|
40
|
+
expect(delayedFn).toHaveBeenCalledTimes(0);
|
|
41
|
+
expect(backOff.getBackOff('test1')).toBeDefined();
|
|
36
42
|
});
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
44
|
+
it('should back off and delay the second execution', async() => {
|
|
45
|
+
const delayedFn = jest.fn();
|
|
46
|
+
const id = 'backoff-test';
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
// First call, which should run immediately.
|
|
49
|
+
await backOff.execute({
|
|
50
|
+
id, description: 'Backoff Test', delayedFn,
|
|
51
|
+
});
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
jest.advanceTimersByTime(1);
|
|
54
|
+
|
|
55
|
+
// Expect the first call to be immediate.
|
|
56
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
// Call it a second time. This should initiate a backoff delay.
|
|
59
|
+
await backOff.execute({
|
|
60
|
+
id, description: 'Backoff Test', delayedFn
|
|
61
|
+
});
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
// The function should not have been called a second time yet.
|
|
64
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
// Advance the timer by less than the first backoff delay (250ms).
|
|
67
|
+
jest.advanceTimersByTime(200);
|
|
68
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
69
|
+
|
|
70
|
+
// Now, advance the timer by the required delay to trigger the second call.
|
|
71
|
+
jest.advanceTimersByTime(50);
|
|
72
|
+
|
|
73
|
+
// The function should have been called a second time.
|
|
74
|
+
await Promise.resolve();
|
|
75
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
76
|
+
|
|
77
|
+
// Verify the backoff entry was created correctly.
|
|
78
|
+
const backOffEntry = backOff.getBackOff(id);
|
|
79
|
+
|
|
80
|
+
expect(backOffEntry.try).toBe(2);
|
|
60
81
|
});
|
|
61
82
|
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
it('should implement exponential backoff on subsequent calls', async() => {
|
|
84
|
+
const delayedFn = jest.fn();
|
|
85
|
+
const id = 'exp-backoff';
|
|
86
|
+
|
|
87
|
+
// First call (immediate)
|
|
88
|
+
await backOff.execute({
|
|
89
|
+
id, description: 'Exponential Backoff Test', delayedFn
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
jest.advanceTimersByTime(1);
|
|
93
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
94
|
+
|
|
95
|
+
// Second call (should have a delay of 1^2 * 250 = 250ms)
|
|
96
|
+
await backOff.execute({
|
|
97
|
+
id, description: 'Exponential Backoff Test', delayedFn
|
|
98
|
+
});
|
|
99
|
+
jest.advanceTimersByTime(250);
|
|
100
|
+
await Promise.resolve();
|
|
101
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
102
|
+
|
|
103
|
+
// Third call (should have a delay of 2^2 * 250 = 1000ms)
|
|
104
|
+
await backOff.execute({
|
|
105
|
+
id, description: 'Exponential Backoff Test', delayedFn
|
|
106
|
+
});
|
|
107
|
+
jest.advanceTimersByTime(1000);
|
|
108
|
+
await Promise.resolve();
|
|
109
|
+
expect(delayedFn).toHaveBeenCalledTimes(3);
|
|
110
|
+
|
|
111
|
+
// Fourth call (should have a delay of 3^2 * 250 = 2250ms)
|
|
112
|
+
await backOff.execute({
|
|
113
|
+
id, description: 'Exponential Backoff Test', delayedFn
|
|
114
|
+
});
|
|
115
|
+
jest.advanceTimersByTime(2250);
|
|
116
|
+
await Promise.resolve();
|
|
117
|
+
expect(delayedFn).toHaveBeenCalledTimes(4);
|
|
118
|
+
});
|
|
64
119
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
120
|
+
it('should skip execution if a previous backoff process is already running', async() => {
|
|
121
|
+
const delayedFn = jest.fn();
|
|
122
|
+
const id = 'skip-test';
|
|
68
123
|
|
|
69
|
-
|
|
70
|
-
|
|
124
|
+
await backOff.execute({
|
|
125
|
+
id, description: 'Skip Test', delayedFn
|
|
126
|
+
});
|
|
127
|
+
expect(delayedFn).toHaveBeenCalledTimes(0);
|
|
71
128
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
129
|
+
// Second call, will be ignored
|
|
130
|
+
await backOff.execute({
|
|
131
|
+
id, description: 'Skip Test', delayedFn
|
|
132
|
+
});
|
|
75
133
|
|
|
76
|
-
|
|
77
|
-
const backOffEntry = backOff.getBackOff(id);
|
|
134
|
+
expect(delayedFn).toHaveBeenCalledTimes(0);
|
|
78
135
|
|
|
79
|
-
|
|
80
|
-
|
|
136
|
+
// We should only have 1 call so far.
|
|
137
|
+
jest.advanceTimersByTime(1);
|
|
138
|
+
await Promise.resolve();
|
|
139
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
81
140
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
141
|
+
// A third call while the first is still pending.
|
|
142
|
+
// This call should be ignored and the delayedFn should not be executed.
|
|
143
|
+
await backOff.execute({
|
|
144
|
+
id, description: 'Skip Test', delayedFn
|
|
145
|
+
});
|
|
85
146
|
|
|
86
|
-
|
|
87
|
-
await backOff.execute({
|
|
88
|
-
id, description: 'Exponential Backoff Test', delayedFn
|
|
89
|
-
});
|
|
147
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
90
148
|
|
|
91
|
-
|
|
92
|
-
|
|
149
|
+
jest.advanceTimersByTime(300);
|
|
150
|
+
await Promise.resolve();
|
|
151
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
93
152
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
153
|
+
// Advance timers to complete the pending second call.
|
|
154
|
+
jest.advanceTimersByTime(1000);
|
|
155
|
+
await Promise.resolve();
|
|
156
|
+
// Now there should be 2 calls, not 3.
|
|
157
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
97
158
|
});
|
|
98
|
-
jest.advanceTimersByTime(250);
|
|
99
|
-
await Promise.resolve();
|
|
100
|
-
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
101
159
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
id
|
|
160
|
+
it('should not execute if the number of retries is exceeded', async() => {
|
|
161
|
+
const delayedFn = jest.fn();
|
|
162
|
+
const id = 'retries-test';
|
|
163
|
+
|
|
164
|
+
// Set retries to 2.
|
|
165
|
+
const retries = 2;
|
|
166
|
+
|
|
167
|
+
// Call 1 (immediate)
|
|
168
|
+
await backOff.execute({
|
|
169
|
+
id, description: 'Retries Test', retries, delayedFn
|
|
170
|
+
});
|
|
171
|
+
jest.advanceTimersByTime(1);
|
|
172
|
+
await Promise.resolve();
|
|
173
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
174
|
+
|
|
175
|
+
// Call 2 (after 250ms delay)
|
|
176
|
+
await backOff.execute({
|
|
177
|
+
id, description: 'Retries Test', retries, delayedFn
|
|
178
|
+
});
|
|
179
|
+
jest.advanceTimersByTime(250);
|
|
180
|
+
await Promise.resolve();
|
|
181
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
182
|
+
|
|
183
|
+
// Call 3 (should be ignored because it exceeds the `retries` limit of 2)
|
|
184
|
+
await backOff.execute({
|
|
185
|
+
id, description: 'Retries Test', retries, delayedFn
|
|
186
|
+
});
|
|
187
|
+
jest.advanceTimersByTime(250);
|
|
188
|
+
await Promise.resolve();
|
|
189
|
+
|
|
190
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
105
191
|
});
|
|
106
|
-
jest.advanceTimersByTime(1000);
|
|
107
|
-
await Promise.resolve();
|
|
108
|
-
expect(delayedFn).toHaveBeenCalledTimes(3);
|
|
109
192
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
jest.advanceTimersByTime(2250);
|
|
115
|
-
await Promise.resolve();
|
|
116
|
-
expect(delayedFn).toHaveBeenCalledTimes(4);
|
|
117
|
-
});
|
|
193
|
+
it('should skip execution if `canFn` returns false', async() => {
|
|
194
|
+
const delayedFn = jest.fn();
|
|
195
|
+
const canFn = jest.fn().mockResolvedValue(false);
|
|
118
196
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
197
|
+
await backOff.execute({
|
|
198
|
+
id: 'canfn-test', description: 'canFn Test', canFn, delayedFn
|
|
199
|
+
});
|
|
122
200
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
});
|
|
126
|
-
expect(delayedFn).toHaveBeenCalledTimes(0);
|
|
201
|
+
jest.advanceTimersByTime(250);
|
|
202
|
+
await Promise.resolve();
|
|
127
203
|
|
|
128
|
-
|
|
129
|
-
await backOff.execute({
|
|
130
|
-
id, description: 'Skip Test', delayedFn
|
|
204
|
+
expect(delayedFn).not.toHaveBeenCalled();
|
|
131
205
|
});
|
|
132
206
|
|
|
133
|
-
|
|
207
|
+
it('should not clear backoff entry if the delayedFn throws an error', async() => {
|
|
208
|
+
const id = 'error-test';
|
|
209
|
+
const delayedFn = jest.fn().mockRejectedValue(new Error('Test Error'));
|
|
134
210
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
211
|
+
// Call the function for the first time.
|
|
212
|
+
await backOff.execute({
|
|
213
|
+
id, description: 'Error Test', delayedFn
|
|
214
|
+
});
|
|
139
215
|
|
|
140
|
-
|
|
141
|
-
// This call should be ignored and the delayedFn should not be executed.
|
|
142
|
-
await backOff.execute({
|
|
143
|
-
id, description: 'Skip Test', delayedFn
|
|
144
|
-
});
|
|
216
|
+
expect(backOff.getBackOff(id).execute?.timeoutId).toBeDefined();
|
|
145
217
|
|
|
146
|
-
|
|
218
|
+
// Wait for the immediate call to finish.
|
|
219
|
+
jest.advanceTimersByTime(1);
|
|
220
|
+
await Promise.resolve();
|
|
221
|
+
await Promise.resolve();
|
|
222
|
+
await Promise.resolve();
|
|
147
223
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
224
|
+
// The entry should be removed after success/failure.
|
|
225
|
+
expect(backOff.getBackOff(id).execute?.timeoutId).toBeUndefined();
|
|
151
226
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
157
|
-
});
|
|
227
|
+
// Call again to trigger a backoff delay and an error.
|
|
228
|
+
await backOff.execute({
|
|
229
|
+
id, description: 'Error Test', delayedFn
|
|
230
|
+
});
|
|
158
231
|
|
|
159
|
-
|
|
160
|
-
const delayedFn = jest.fn();
|
|
161
|
-
const id = 'retries-test';
|
|
232
|
+
expect(backOff.getBackOff(id).execute?.timeoutId).toBeDefined();
|
|
162
233
|
|
|
163
|
-
|
|
164
|
-
|
|
234
|
+
// Advance timers to trigger the delayed function.
|
|
235
|
+
jest.advanceTimersByTime(250);
|
|
236
|
+
await Promise.resolve();
|
|
237
|
+
await Promise.resolve();
|
|
238
|
+
await Promise.resolve();
|
|
165
239
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
id, description: 'Retries Test', retries, delayedFn
|
|
169
|
-
});
|
|
170
|
-
jest.advanceTimersByTime(1);
|
|
171
|
-
await Promise.resolve();
|
|
172
|
-
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
240
|
+
// The timeoutId should be cleared, but the `try` count should be preserved on the next call.
|
|
241
|
+
expect(backOff.getBackOff(id).execute?.timeoutId).toBeUndefined();
|
|
173
242
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
id, description: 'Retries Test', retries, delayedFn
|
|
177
|
-
});
|
|
178
|
-
jest.advanceTimersByTime(250);
|
|
179
|
-
await Promise.resolve();
|
|
180
|
-
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
243
|
+
// Check if the next call will still back off.
|
|
244
|
+
const newDelayedFn = jest.fn();
|
|
181
245
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
246
|
+
await backOff.execute({
|
|
247
|
+
id, description: 'Error Test', delayedFn: newDelayedFn
|
|
248
|
+
});
|
|
249
|
+
expect(newDelayedFn).not.toHaveBeenCalled(); // The next call should still be delayed
|
|
185
250
|
});
|
|
186
|
-
jest.advanceTimersByTime(250);
|
|
187
|
-
await Promise.resolve();
|
|
188
251
|
|
|
189
|
-
|
|
190
|
-
|
|
252
|
+
it('should save metadata', async() => {
|
|
253
|
+
const delayedFn = jest.fn();
|
|
254
|
+
const id = 'exp-backoff';
|
|
255
|
+
const metadata = { a: true };
|
|
191
256
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
257
|
+
// First call (immediate)
|
|
258
|
+
await backOff.execute({
|
|
259
|
+
id, description: 'Exponential Backoff Test', delayedFn, metadata
|
|
260
|
+
});
|
|
195
261
|
|
|
196
|
-
|
|
197
|
-
id
|
|
262
|
+
expect(backOff.getBackOff(id)).toBeDefined();
|
|
263
|
+
expect(backOff.getBackOff(id).metadata).toStrictEqual(metadata);
|
|
198
264
|
});
|
|
265
|
+
});
|
|
199
266
|
|
|
200
|
-
|
|
201
|
-
|
|
267
|
+
describe('reset', () => {
|
|
268
|
+
beforeEach(beforeEachFn);
|
|
202
269
|
|
|
203
|
-
|
|
204
|
-
});
|
|
270
|
+
afterEach(afterEachFn);
|
|
205
271
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
272
|
+
it('should reset a specific backoff process', async() => {
|
|
273
|
+
const delayedFn = jest.fn();
|
|
274
|
+
const id = 'reset-test';
|
|
209
275
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
276
|
+
// Start a backoff process.
|
|
277
|
+
await backOff.execute({
|
|
278
|
+
id, description: 'Reset Test', delayedFn
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
expect(backOff.getBackOff(id)).toBeDefined();
|
|
214
282
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await Promise.resolve();
|
|
218
|
-
await Promise.resolve();
|
|
219
|
-
await Promise.resolve();
|
|
283
|
+
// Reset the process.
|
|
284
|
+
backOff.reset(id);
|
|
220
285
|
|
|
221
|
-
|
|
222
|
-
|
|
286
|
+
// The entry should be deleted from the map.
|
|
287
|
+
expect(backOff.getBackOff(id)).toBeUndefined();
|
|
223
288
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
289
|
+
// Now, a new execution should not be delayed.
|
|
290
|
+
await backOff.execute({
|
|
291
|
+
id, description: 'Reset Test', delayedFn
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
jest.advanceTimersByTime(250);
|
|
295
|
+
await Promise.resolve();
|
|
296
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
227
297
|
});
|
|
228
298
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
await Promise.resolve();
|
|
233
|
-
await Promise.resolve();
|
|
299
|
+
it('should reset all backoff processes', async() => {
|
|
300
|
+
const delayedFn1 = jest.fn();
|
|
301
|
+
const delayedFn2 = jest.fn();
|
|
234
302
|
|
|
235
|
-
|
|
236
|
-
|
|
303
|
+
await backOff.execute({
|
|
304
|
+
id: 'all-1', description: 'All 1', delayedFn: delayedFn1
|
|
305
|
+
});
|
|
306
|
+
await backOff.execute({
|
|
307
|
+
id: 'all-1', description: 'All 1', delayedFn: delayedFn1
|
|
308
|
+
});
|
|
237
309
|
|
|
238
|
-
|
|
239
|
-
|
|
310
|
+
await backOff.execute({
|
|
311
|
+
id: 'all-2', description: 'All 2', delayedFn: delayedFn2
|
|
312
|
+
});
|
|
313
|
+
await backOff.execute({
|
|
314
|
+
id: 'all-2', description: 'All 2', delayedFn: delayedFn2
|
|
315
|
+
});
|
|
240
316
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
});
|
|
244
|
-
expect(newDelayedFn).not.toHaveBeenCalled(); // The next call should still be delayed
|
|
245
|
-
});
|
|
317
|
+
expect(backOff.getBackOff('all-1')).toBeDefined();
|
|
318
|
+
expect(backOff.getBackOff('all-2')).toBeDefined();
|
|
246
319
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const id = 'exp-backoff';
|
|
250
|
-
const metadata = { a: true };
|
|
320
|
+
// Reset all processes.
|
|
321
|
+
backOff.resetAll();
|
|
251
322
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
id, description: 'Exponential Backoff Test', delayedFn, metadata
|
|
323
|
+
expect(backOff.getBackOff('all-1')).toBeUndefined();
|
|
324
|
+
expect(backOff.getBackOff('all-2')).toBeUndefined();
|
|
255
325
|
});
|
|
256
326
|
|
|
257
|
-
|
|
258
|
-
|
|
327
|
+
it('should reset only processes with a specific prefix', async() => {
|
|
328
|
+
const delayedFn = jest.fn();
|
|
329
|
+
|
|
330
|
+
await backOff.execute({
|
|
331
|
+
id: 'prefix-test-1', description: 'Prefix Test 1', delayedFn
|
|
332
|
+
});
|
|
333
|
+
await backOff.execute({
|
|
334
|
+
id: 'prefix-test-1', description: 'Prefix Test 1', delayedFn
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await backOff.execute({
|
|
338
|
+
id: 'prefix-test-2', description: 'Prefix Test 2', delayedFn
|
|
339
|
+
});
|
|
340
|
+
await backOff.execute({
|
|
341
|
+
id: 'prefix-test-2', description: 'Prefix Test 2', delayedFn
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await backOff.execute({
|
|
345
|
+
id: 'other-test-1', description: 'Other Test 1', delayedFn
|
|
346
|
+
});
|
|
347
|
+
await backOff.execute({
|
|
348
|
+
id: 'other-test-1', description: 'Other Test 1', delayedFn
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(backOff.getBackOff('prefix-test-1')).toBeDefined();
|
|
352
|
+
expect(backOff.getBackOff('prefix-test-2')).toBeDefined();
|
|
353
|
+
expect(backOff.getBackOff('other-test-1')).toBeDefined();
|
|
354
|
+
|
|
355
|
+
// Reset only the "prefix-test" processes.
|
|
356
|
+
backOff.resetPrefix('prefix-test');
|
|
357
|
+
|
|
358
|
+
expect(backOff.getBackOff('prefix-test-1')).toBeUndefined();
|
|
359
|
+
expect(backOff.getBackOff('prefix-test-2')).toBeUndefined();
|
|
360
|
+
// The other process should still exist.
|
|
361
|
+
expect(backOff.getBackOff('other-test-1')).toBeDefined();
|
|
362
|
+
});
|
|
259
363
|
});
|
|
260
364
|
|
|
261
|
-
|
|
365
|
+
describe('recurse', () => {
|
|
366
|
+
beforeEach(beforeEachFn);
|
|
262
367
|
|
|
263
|
-
|
|
264
|
-
const delayedFn = jest.fn();
|
|
265
|
-
const id = 'reset-test';
|
|
368
|
+
afterEach(afterEachFn);
|
|
266
369
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
370
|
+
const handlePromises = async() => {
|
|
371
|
+
for (let i = 0; i < 10; i++) {
|
|
372
|
+
await Promise.resolve();
|
|
373
|
+
}
|
|
374
|
+
};
|
|
271
375
|
|
|
272
|
-
|
|
376
|
+
const iterationLoop = async(wait: number) => {
|
|
377
|
+
await handlePromises();
|
|
273
378
|
|
|
274
|
-
|
|
275
|
-
backOff.reset(id);
|
|
379
|
+
jest.advanceTimersByTime(wait);
|
|
276
380
|
|
|
277
|
-
|
|
278
|
-
|
|
381
|
+
await handlePromises();
|
|
382
|
+
};
|
|
279
383
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
id
|
|
283
|
-
});
|
|
384
|
+
it('should execute the function immediately', async() => {
|
|
385
|
+
const delayedFn = jest.fn().mockResolvedValue('success');
|
|
386
|
+
const id = 'recurse-test';
|
|
284
387
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
});
|
|
388
|
+
const promise = backOff.recurse({
|
|
389
|
+
id, description: 'Recurse Test', delayedFn, continueOnError: async() => false
|
|
390
|
+
});
|
|
289
391
|
|
|
290
|
-
|
|
291
|
-
const delayedFn1 = jest.fn();
|
|
292
|
-
const delayedFn2 = jest.fn();
|
|
392
|
+
await iterationLoop(1);
|
|
293
393
|
|
|
294
|
-
|
|
295
|
-
id: 'all-1', description: 'All 1', delayedFn: delayedFn1
|
|
296
|
-
});
|
|
297
|
-
await backOff.execute({
|
|
298
|
-
id: 'all-1', description: 'All 1', delayedFn: delayedFn1
|
|
299
|
-
});
|
|
394
|
+
const result = await promise;
|
|
300
395
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
});
|
|
304
|
-
await backOff.execute({
|
|
305
|
-
id: 'all-2', description: 'All 2', delayedFn: delayedFn2
|
|
396
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
397
|
+
expect(result).toBe('success');
|
|
306
398
|
});
|
|
307
399
|
|
|
308
|
-
|
|
309
|
-
|
|
400
|
+
it('should retry on failure if continueOnError returns true', async() => {
|
|
401
|
+
const delayedFn = jest.fn()
|
|
402
|
+
.mockRejectedValueOnce(new Error('Fail 1'))
|
|
403
|
+
.mockResolvedValue('success');
|
|
404
|
+
const id = 'recurse-retry';
|
|
405
|
+
const continueOnError = jest.fn().mockResolvedValue(true);
|
|
310
406
|
|
|
311
|
-
|
|
312
|
-
|
|
407
|
+
const promise = backOff.recurse({
|
|
408
|
+
id, description: 'Recurse Retry', delayedFn, continueOnError
|
|
409
|
+
});
|
|
313
410
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
});
|
|
411
|
+
// First attempt (i=0)
|
|
412
|
+
await iterationLoop(1);
|
|
317
413
|
|
|
318
|
-
|
|
319
|
-
|
|
414
|
+
// Second attempt (i=1), delay 250ms
|
|
415
|
+
await iterationLoop(250);
|
|
320
416
|
|
|
321
|
-
|
|
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
|
-
});
|
|
417
|
+
const result = await promise;
|
|
327
418
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
});
|
|
331
|
-
await backOff.execute({
|
|
332
|
-
id: 'prefix-test-2', description: 'Prefix Test 2', delayedFn
|
|
419
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
420
|
+
expect(result).toBe('success');
|
|
333
421
|
});
|
|
334
422
|
|
|
335
|
-
|
|
336
|
-
|
|
423
|
+
it('should stop retrying if continueOnError returns false', async() => {
|
|
424
|
+
const delayedFn = jest.fn().mockRejectedValue(new Error('Fail'));
|
|
425
|
+
const id = 'recurse-stop';
|
|
426
|
+
const continueOnError = jest.fn().mockResolvedValue(false);
|
|
427
|
+
|
|
428
|
+
const promise = backOff.recurse({
|
|
429
|
+
id, description: 'Recurse Stop', delayedFn, continueOnError
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
await iterationLoop(1);
|
|
433
|
+
|
|
434
|
+
await expect(promise).rejects.toThrow('Failed call');
|
|
435
|
+
expect(delayedFn).toHaveBeenCalledTimes(1);
|
|
337
436
|
});
|
|
338
|
-
|
|
339
|
-
|
|
437
|
+
|
|
438
|
+
it('should stop retrying if max retries reached', async() => {
|
|
439
|
+
const delayedFn = jest.fn().mockRejectedValue(new Error('Fail'));
|
|
440
|
+
const id = 'recurse-max';
|
|
441
|
+
const continueOnError = jest.fn().mockResolvedValue(true);
|
|
442
|
+
const retries = 2;
|
|
443
|
+
|
|
444
|
+
const promise = backOff.recurse({
|
|
445
|
+
id, description: 'Recurse Max', delayedFn, continueOnError, retries
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Try 1
|
|
449
|
+
await iterationLoop(1);
|
|
450
|
+
|
|
451
|
+
// Try 2
|
|
452
|
+
await iterationLoop(250);
|
|
453
|
+
|
|
454
|
+
const result = await promise;
|
|
455
|
+
|
|
456
|
+
expect(result).toBeUndefined();
|
|
457
|
+
expect(delayedFn).toHaveBeenCalledTimes(2);
|
|
340
458
|
});
|
|
341
459
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
460
|
+
it('should skip if canFn returns false', async() => {
|
|
461
|
+
const delayedFn = jest.fn();
|
|
462
|
+
const id = 'recurse-canfn';
|
|
463
|
+
const canFn = jest.fn().mockResolvedValue(false);
|
|
345
464
|
|
|
346
|
-
|
|
347
|
-
|
|
465
|
+
const promise = backOff.recurse({
|
|
466
|
+
id, description: 'Recurse CanFn', delayedFn, continueOnError: async() => false, canFn
|
|
467
|
+
});
|
|
348
468
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
expect(backOff.getBackOff('other-test-1')).toBeDefined();
|
|
469
|
+
await expect(promise).rejects.toThrow('Skipping (canFn test failed)');
|
|
470
|
+
expect(delayedFn).not.toHaveBeenCalled();
|
|
471
|
+
});
|
|
353
472
|
});
|
|
354
473
|
});
|