@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.
- package/assets/brand/classic/metadata.json +3 -0
- package/assets/styles/app.scss +1 -0
- package/assets/styles/base/_color.scss +16 -0
- package/assets/styles/base/_helpers.scss +10 -0
- package/assets/styles/base/_variables.scss +18 -12
- package/assets/styles/fonts/_icons.scss +1 -32
- package/assets/styles/global/_layout.scss +1 -1
- package/assets/styles/themes/_dark.scss +262 -258
- package/assets/styles/themes/_light.scss +538 -509
- package/assets/styles/themes/_modern.scss +914 -0
- package/assets/translations/en-us.yaml +110 -29
- package/chart/__tests__/S3.test.ts +2 -1
- package/cloud-credential/generic.vue +18 -10
- package/cloud-credential/harvester.vue +1 -9
- package/components/AdvancedSection.vue +8 -0
- package/components/ChartReadme.vue +17 -7
- package/components/CodeMirror.vue +1 -1
- package/components/Drawer/Chrome.vue +0 -1
- package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
- package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
- package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
- package/components/InstallHelmCharts.vue +656 -0
- package/components/LazyImage.vue +60 -4
- package/components/Loading.vue +1 -1
- package/components/LocaleSelector.vue +7 -2
- package/components/Markdown.vue +4 -0
- package/components/PaginatedResourceTable.vue +46 -1
- package/components/PromptRestore.vue +22 -44
- package/components/Resource/Detail/Masthead/composable.ts +16 -0
- package/components/Resource/Detail/Masthead/index.vue +37 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
- package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
- package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
- package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
- package/components/Resource/Detail/Metadata/composables.ts +9 -7
- package/components/Resource/Detail/Metadata/index.vue +17 -2
- package/components/Resource/Detail/Page.vue +35 -21
- package/components/Resource/Detail/SpacedRow.vue +1 -1
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
- package/components/Resource/Detail/TitleBar/composables.ts +5 -5
- package/components/Resource/Detail/TitleBar/index.vue +12 -3
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/ResourceDetail/index.vue +569 -72
- package/components/ResourceList/index.vue +1 -0
- package/components/ResourceTable.vue +6 -1
- package/components/ResourceYaml.vue +1 -1
- package/components/RichTranslation.vue +106 -0
- package/components/SlideInPanelManager.vue +13 -10
- package/components/SortableTable/index.vue +5 -5
- package/components/SortableTable/selection.js +0 -1
- package/components/Tabbed/index.vue +35 -4
- package/components/__tests__/LazyImage.spec.ts +121 -0
- package/components/__tests__/PromptRestore.test.ts +1 -65
- package/components/__tests__/RichTranslation.test.ts +115 -0
- package/components/fleet/FleetStatus.vue +4 -0
- package/components/fleet/dashboard/ResourcePanel.vue +2 -1
- package/components/form/ClusterAppearance.vue +5 -0
- package/components/form/FileImageSelector.vue +1 -1
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/NameNsDescription.vue +1 -0
- package/components/form/Networking.vue +24 -19
- package/components/form/ProjectMemberEditor.vue +1 -1
- package/components/form/ResourceLabeledSelect.vue +22 -8
- package/components/form/ResourceTabs/index.vue +20 -0
- package/components/form/SecretSelector.vue +9 -0
- package/components/form/SelectOrCreateAuthSecret.vue +6 -3
- package/components/form/__tests__/Networking.test.ts +116 -0
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
- package/components/formatter/FleetApplicationSource.vue +25 -17
- package/components/formatter/PodImages.vue +1 -1
- package/components/formatter/__tests__/LiveDate.test.ts +10 -2
- package/components/google/AccountAccess.vue +44 -46
- package/components/nav/Favorite.vue +4 -0
- package/components/nav/Group.vue +4 -1
- package/components/nav/NotificationCenter/Notification.vue +1 -27
- package/components/nav/WindowManager/index.vue +3 -3
- package/composables/resources.ts +2 -2
- package/config/labels-annotations.js +3 -2
- package/config/pagination-table-headers.js +8 -1
- package/config/product/explorer.js +27 -2
- package/config/product/manager.js +0 -1
- package/config/query-params.js +10 -0
- package/config/router/routes.js +21 -1
- package/config/system-namespaces.js +1 -1
- package/config/table-headers.js +30 -1
- package/config/types.js +1 -1
- package/config/version.js +1 -1
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
- package/detail/__tests__/workload.test.ts +164 -0
- package/detail/configmap.vue +33 -75
- package/detail/projectsecret.vue +11 -0
- package/detail/provisioning.cattle.io.cluster.vue +351 -369
- package/detail/secret.vue +49 -308
- package/detail/workload/index.vue +38 -21
- package/dialog/InstallExtensionDialog.vue +8 -5
- package/dialog/RotateEncryptionKeyDialog.vue +10 -30
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
- package/edit/auth/ldap/__tests__/config.test.ts +14 -0
- package/edit/auth/ldap/config.vue +24 -0
- package/edit/compliance.cattle.io.clusterscan.vue +1 -1
- package/edit/configmap.vue +4 -1
- package/edit/fleet.cattle.io.gitrepo.vue +5 -6
- package/edit/fleet.cattle.io.helmop.vue +78 -56
- package/edit/logging.banzaicloud.io.output/index.vue +1 -1
- package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
- package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
- package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
- package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
- package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
- package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
- package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
- package/edit/secret/basic.vue +1 -0
- package/edit/secret/index.vue +126 -15
- package/edit/workload/index.vue +5 -14
- package/list/projectsecret.vue +345 -0
- package/list/provisioning.cattle.io.cluster.vue +1 -69
- package/list/secret.vue +109 -0
- package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
- package/machine-config/google.vue +9 -1
- package/machine-config/vmwarevsphere.vue +7 -17
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/chart.js +0 -2
- package/mixins/create-edit-view/impl.js +10 -1
- package/mixins/resource-fetch-api-pagination.js +11 -12
- package/mixins/resource-fetch.js +3 -1
- package/models/__tests__/chart.test.ts +111 -80
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
- package/models/__tests__/node.test.ts +7 -63
- package/models/catalog.cattle.io.app.js +1 -1
- package/models/catalog.cattle.io.operation.js +1 -1
- package/models/chart.js +36 -20
- package/models/cloudcredential.js +2 -163
- package/models/cluster/node.js +7 -7
- package/models/cluster.x-k8s.io.machine.js +3 -3
- package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
- package/models/compliance.cattle.io.clusterscan.js +2 -2
- package/models/configmap.js +4 -0
- package/models/constraints.gatekeeper.sh.constraint.js +1 -1
- package/models/fleet-application.js +0 -17
- package/models/fleet.cattle.io.cluster.js +2 -2
- package/models/fleet.cattle.io.gitrepo.js +15 -1
- package/models/fleet.cattle.io.helmop.js +26 -22
- package/models/management.cattle.io.setting.js +4 -0
- package/models/persistentvolumeclaim.js +1 -1
- package/models/pod.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +39 -67
- package/models/rke.cattle.io.etcdsnapshot.js +1 -1
- package/models/secret.js +161 -2
- package/models/storage.k8s.io.storageclass.js +2 -2
- package/models/workload.js +3 -3
- package/package.json +11 -10
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
- package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
- package/pages/c/_cluster/apps/charts/chart.vue +422 -174
- package/pages/c/_cluster/apps/charts/index.vue +46 -35
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
- package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
- package/pages/c/_cluster/fleet/index.vue +103 -45
- package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
- package/pages/c/_cluster/uiplugins/index.vue +36 -25
- package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
- package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
- package/plugins/dashboard-store/actions.js +42 -22
- package/plugins/dashboard-store/normalize.js +29 -17
- package/plugins/dashboard-store/resource-class.js +83 -17
- package/plugins/steve/__tests__/getters.test.ts +1 -1
- package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
- package/plugins/steve/getters.js +8 -2
- package/plugins/steve/resourceWatcher.js +10 -3
- package/plugins/steve/steve-pagination-utils.ts +14 -3
- package/plugins/steve/subscribe.js +192 -19
- package/plugins/steve/worker/web-worker.advanced.js +2 -0
- package/rancher-components/Card/Card.vue +0 -18
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
- package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
- package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
- package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
- package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
- package/rancher-components/Pill/types.ts +2 -0
- package/rancher-components/RcButton/RcButton.vue +1 -1
- package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
- package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
- package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
- package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
- package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
- package/store/__tests__/catalog.test.ts +93 -1
- package/store/aws.js +19 -8
- package/store/catalog.js +8 -3
- package/types/kube/kube-api.ts +12 -0
- package/types/resources/settings.d.ts +1 -1
- package/types/shell/index.d.ts +643 -585
- package/types/store/pagination.types.ts +16 -6
- package/types/uiplugins.ts +73 -0
- package/utils/__tests__/back-off.test.ts +354 -0
- package/utils/__tests__/create-yaml.test.ts +235 -0
- package/utils/__tests__/kontainer.test.ts +19 -0
- package/utils/__tests__/uiplugins.test.ts +84 -0
- package/utils/back-off.ts +176 -0
- package/utils/create-yaml.js +103 -9
- package/utils/dynamic-importer.js +8 -0
- package/utils/kontainer.ts +3 -5
- package/utils/pagination-utils.ts +18 -0
- package/utils/style.ts +3 -0
- package/utils/uiplugins.ts +29 -2
- package/utils/validators/__tests__/setting.test.js +92 -0
- package/utils/validators/formRules/__tests__/index.test.ts +88 -7
- package/utils/validators/formRules/index.ts +83 -8
- package/utils/validators/setting.js +17 -0
- package/cloud-credential/__tests__/harvester.test.ts +0 -18
- package/components/ResourceDetail/__tests__/index.test.ts +0 -135
- package/components/ResourceDetail/legacy.vue +0 -562
- package/components/formatter/CloudCredExpired.vue +0 -69
- package/models/etcdbackup.js +0 -45
- package/pages/explorer/resource/detail/configmap.vue +0 -42
- package/pages/explorer/resource/detail/secret.vue +0 -50
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
});
|