@rancher/shell 3.0.7 → 3.0.8-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/assets/images/vendor/githubapp.svg +13 -0
- package/assets/styles/base/_typography.scss +1 -1
- package/assets/styles/global/_layout.scss +21 -35
- package/assets/styles/themes/_modern.scss +5 -5
- package/assets/translations/en-us.yaml +102 -17
- package/assets/translations/zh-hans.yaml +0 -4
- package/components/EmberPage.vue +1 -1
- package/components/Inactivity.vue +222 -106
- package/components/InstallHelmCharts.vue +2 -2
- package/components/Resource/Detail/CopyToClipboard.vue +1 -1
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
- package/components/Resource/Detail/TitleBar/index.vue +10 -6
- package/components/ResourceDetail/index.vue +4 -1
- package/components/SortableTable/index.vue +18 -2
- package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
- package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
- package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
- package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
- package/components/fleet/FleetConfigMapSelector.vue +117 -0
- package/components/fleet/FleetSecretSelector.vue +127 -0
- package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
- package/components/form/FileImageSelector.vue +13 -4
- package/components/form/FileSelector.vue +11 -2
- package/components/form/ResourceLabeledSelect.vue +1 -0
- package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
- package/components/nav/Header.vue +34 -13
- package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
- package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
- package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
- package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
- package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
- package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
- package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
- package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
- package/components/nav/WindowManager/constants.ts +23 -0
- package/components/nav/WindowManager/index.vue +61 -575
- package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
- package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
- package/components/templates/default.vue +4 -40
- package/components/templates/home.vue +31 -5
- package/config/product/auth.js +1 -0
- package/config/query-params.js +1 -0
- package/config/settings.ts +8 -1
- package/config/store.js +4 -2
- package/config/types.js +2 -0
- package/detail/pod.vue +1 -0
- package/dialog/AddonConfigConfirmationDialog.vue +45 -1
- package/directives/ui-context.ts +97 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
- package/edit/auth/AuthProviderWarningBanners.vue +14 -1
- package/edit/auth/github-app-steps.vue +97 -0
- package/edit/auth/github-steps.vue +75 -0
- package/edit/auth/github.vue +94 -65
- package/edit/fleet.cattle.io.helmop.vue +51 -2
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
- package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
- package/initialize/install-directives.js +2 -0
- package/list/projectsecret.vue +1 -1
- package/machine-config/azure.vue +1 -1
- package/mixins/chart.js +1 -1
- package/models/__tests__/chart.test.ts +17 -9
- package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
- package/models/catalog.cattle.io.app.js +1 -1
- package/models/chart.js +3 -1
- package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
- package/models/management.cattle.io.authconfig.js +1 -0
- package/package.json +2 -2
- package/pages/auth/login.vue +5 -2
- package/pages/auth/verify.vue +1 -1
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
- package/pages/c/_cluster/apps/charts/chart.vue +2 -2
- package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
- package/pages/c/_cluster/explorer/tools/index.vue +3 -3
- package/pages/c/_cluster/settings/performance.vue +12 -25
- package/pages/home.vue +313 -12
- package/plugins/axios.js +2 -1
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/resource-class.js +17 -2
- package/plugins/steve/steve-pagination-utils.ts +2 -2
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
- package/scripts/extension/publish +1 -1
- package/store/auth.js +8 -3
- package/store/aws.js +8 -6
- package/store/features.js +1 -0
- package/store/index.js +9 -3
- package/store/prefs.js +6 -0
- package/store/ui-context.ts +86 -0
- package/store/wm.ts +244 -0
- package/types/kube/kube-api.ts +2 -1
- package/types/rancher/index.d.ts +1 -0
- package/types/resources/settings.d.ts +29 -7
- package/types/shell/index.d.ts +59 -0
- package/types/window-manager.ts +22 -0
- package/utils/__tests__/cluster.test.ts +379 -1
- package/utils/cluster.js +157 -3
- package/utils/dynamic-content/__tests__/config.test.ts +187 -0
- package/utils/dynamic-content/__tests__/index.test.ts +390 -0
- package/utils/dynamic-content/__tests__/info.test.ts +263 -0
- package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
- package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
- package/utils/dynamic-content/__tests__/util.test.ts +235 -0
- package/utils/dynamic-content/config.ts +55 -0
- package/utils/dynamic-content/index.ts +273 -0
- package/utils/dynamic-content/info.ts +219 -0
- package/utils/dynamic-content/new-release.ts +126 -0
- package/utils/dynamic-content/support-notice.ts +169 -0
- package/utils/dynamic-content/types.d.ts +101 -0
- package/utils/dynamic-content/util.ts +122 -0
- package/utils/dynamic-importer.js +2 -2
- package/utils/inactivity.ts +104 -0
- package/utils/pagination-utils.ts +19 -4
- package/utils/release-notes.ts +1 -1
- package/assets/images/icons/document.svg +0 -3
- package/store/wm.js +0 -95
- /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
- /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
- /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { getConfig } from '../config';
|
|
2
|
+
import { SETTING } from '@shell/config/settings';
|
|
3
|
+
import * as version from '@shell/config/version';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ENDPOINT = 'https://updates.rancher.io/rancher/$dist/updates';
|
|
6
|
+
|
|
7
|
+
// Mock dependencies
|
|
8
|
+
jest.mock('@shell/config/version', () => ({ getVersionData: jest.fn(), isRancherPrime: jest.fn() }));
|
|
9
|
+
|
|
10
|
+
describe('getConfig', () => {
|
|
11
|
+
let mockGetters: any;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Reset mocks before each test
|
|
15
|
+
(version.getVersionData as jest.Mock).mockClear();
|
|
16
|
+
(version.isRancherPrime as jest.Mock).mockClear();
|
|
17
|
+
|
|
18
|
+
// Default mock for getters
|
|
19
|
+
mockGetters = { 'management/byId': jest.fn() };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('community distribution', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
(version.getVersionData as jest.Mock).mockReturnValue({ RancherPrime: 'false' });
|
|
25
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return default community config when no settings are present', () => {
|
|
29
|
+
mockGetters['management/byId'].mockReturnValue(null);
|
|
30
|
+
const config = getConfig(mockGetters);
|
|
31
|
+
|
|
32
|
+
expect(config.prime).toBe(false);
|
|
33
|
+
expect(config.distribution).toBe('community');
|
|
34
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
35
|
+
expect(config.enabled).toBe(true);
|
|
36
|
+
expect(config.debug).toBe(false);
|
|
37
|
+
expect(config.log).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should enable debug and log modes when enabled setting is "debug"', () => {
|
|
41
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
42
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
43
|
+
return { value: 'debug' };
|
|
44
|
+
}
|
|
45
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
46
|
+
return { value: 'https://test.endpoint' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const config = getConfig(mockGetters);
|
|
53
|
+
|
|
54
|
+
expect(config.enabled).toBe(true);
|
|
55
|
+
expect(config.debug).toBe(true);
|
|
56
|
+
expect(config.log).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should enable log mode when enabled setting is "log"', () => {
|
|
60
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
61
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
62
|
+
return { value: 'log' };
|
|
63
|
+
}
|
|
64
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
65
|
+
return { value: 'https://test.endpoint' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const config = getConfig(mockGetters);
|
|
72
|
+
|
|
73
|
+
expect(config.enabled).toBe(true);
|
|
74
|
+
expect(config.debug).toBe(false);
|
|
75
|
+
expect(config.log).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should not use the endpoint from settings when community', () => {
|
|
79
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
80
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
81
|
+
return { value: 'https://custom.endpoint/rancher/$dist' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const config = getConfig(mockGetters);
|
|
88
|
+
|
|
89
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
90
|
+
expect(config.enabled).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('prime distribution', () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
(version.getVersionData as jest.Mock).mockReturnValue({ RancherPrime: 'true' });
|
|
97
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should return default prime config when no settings are present', () => {
|
|
101
|
+
mockGetters['management/byId'].mockReturnValue(null);
|
|
102
|
+
const config = getConfig(mockGetters);
|
|
103
|
+
|
|
104
|
+
expect(config.prime).toBe(true);
|
|
105
|
+
expect(config.distribution).toBe('prime');
|
|
106
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
107
|
+
expect(config.enabled).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should be disabled when the setting is "false"', () => {
|
|
111
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
112
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
113
|
+
return { value: 'false' };
|
|
114
|
+
}
|
|
115
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
116
|
+
return { value: 'https://test.endpoint' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return null;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const config = getConfig(mockGetters);
|
|
123
|
+
|
|
124
|
+
expect(config.enabled).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should be enabled when the setting is "true"', () => {
|
|
128
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
129
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
130
|
+
return { value: 'true' };
|
|
131
|
+
}
|
|
132
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
133
|
+
return { value: 'https://test.endpoint' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const config = getConfig(mockGetters);
|
|
140
|
+
|
|
141
|
+
expect(config.enabled).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should NOT use the endpoint from settings if it is not a valid HTTPS URL', () => {
|
|
145
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
146
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
147
|
+
return { value: 'http://insecure.endpoint/rancher/$dist' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return null;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const config = getConfig(mockGetters);
|
|
154
|
+
|
|
155
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT); // Falls back to default
|
|
156
|
+
expect(config.enabled).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('general', () => {
|
|
161
|
+
it('should handle exceptions when reading settings and return defaults', () => {
|
|
162
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
163
|
+
const testError = new Error('Something went wrong');
|
|
164
|
+
|
|
165
|
+
(version.getVersionData as jest.Mock).mockReturnValue({ RancherPrime: 'false' });
|
|
166
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(false);
|
|
167
|
+
mockGetters['management/byId'].mockImplementation(() => {
|
|
168
|
+
throw testError;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const config = getConfig(mockGetters);
|
|
172
|
+
|
|
173
|
+
// It should fall back to the default configuration
|
|
174
|
+
expect(config.prime).toBe(false);
|
|
175
|
+
expect(config.distribution).toBe('community');
|
|
176
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
177
|
+
expect(config.enabled).toBe(true);
|
|
178
|
+
expect(config.debug).toBe(false);
|
|
179
|
+
expect(config.log).toBe(false);
|
|
180
|
+
|
|
181
|
+
// And it should log the error
|
|
182
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error reading dynamic content settings', testError);
|
|
183
|
+
|
|
184
|
+
consoleErrorSpy.mockRestore();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { fetchAndProcessDynamicContent, fetchDynamicContent, UPDATE_DATE_FORMAT } from '../index';
|
|
2
|
+
import * as config from '../config';
|
|
3
|
+
import * as util from '../util';
|
|
4
|
+
import * as newRelease from '../new-release';
|
|
5
|
+
import * as supportNotice from '../support-notice';
|
|
6
|
+
import * as info from '../info';
|
|
7
|
+
import * as typeMap from '@shell/store/type-map';
|
|
8
|
+
import * as version from '@shell/config/version';
|
|
9
|
+
import * as jsyaml from 'js-yaml';
|
|
10
|
+
import dayjs from 'dayjs';
|
|
11
|
+
import { Context } from '../types';
|
|
12
|
+
|
|
13
|
+
// Mock dependencies
|
|
14
|
+
jest.mock('../config');
|
|
15
|
+
jest.mock('../util');
|
|
16
|
+
jest.mock('../new-release');
|
|
17
|
+
jest.mock('../support-notice');
|
|
18
|
+
jest.mock('../info');
|
|
19
|
+
jest.mock('@shell/store/type-map');
|
|
20
|
+
jest.mock('@shell/config/version');
|
|
21
|
+
jest.mock('js-yaml');
|
|
22
|
+
|
|
23
|
+
// Mock dayjs to control time
|
|
24
|
+
const mockDayInstance = {
|
|
25
|
+
format: jest.fn(() => '2023-01-01'),
|
|
26
|
+
add: jest.fn((amount, unit) => {
|
|
27
|
+
return dayjs('2023-01-01').add(amount, unit);
|
|
28
|
+
}),
|
|
29
|
+
diff: jest.fn(() => 100), // a value > 30 for concurrent check
|
|
30
|
+
toString: jest.fn(() => new Date('2023-01-01T12:00:00Z').toString()),
|
|
31
|
+
isAfter: jest.fn(),
|
|
32
|
+
isValid: jest.fn(() => true),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const mockGetConfig = config.getConfig as jest.Mock;
|
|
36
|
+
const mockCreateLogger = util.createLogger as jest.Mock;
|
|
37
|
+
const mockProcessReleaseVersion = newRelease.processReleaseVersion as jest.Mock;
|
|
38
|
+
const mockProcessSupportNotices = supportNotice.processSupportNotices as jest.Mock;
|
|
39
|
+
const mockIsAdminUser = typeMap.isAdminUser as jest.Mock;
|
|
40
|
+
const mockGetVersionData = version.getVersionData as jest.Mock;
|
|
41
|
+
const mockYamlLoad = jsyaml.load as jest.Mock;
|
|
42
|
+
const mockSystemInfoProvider = info.SystemInfoProvider as jest.Mock;
|
|
43
|
+
|
|
44
|
+
describe('dynamic content', () => {
|
|
45
|
+
let mockDispatch: jest.Mock;
|
|
46
|
+
let mockGetters: any;
|
|
47
|
+
let mockAxios: jest.Mock;
|
|
48
|
+
let mockLogger: any;
|
|
49
|
+
let mockConfig: any;
|
|
50
|
+
let localStorageMock: any;
|
|
51
|
+
let consoleLogSpy: jest.SpyInstance;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
mockDispatch = jest.fn();
|
|
55
|
+
mockGetters = { isSingleProduct: false };
|
|
56
|
+
mockAxios = jest.fn();
|
|
57
|
+
mockLogger = {
|
|
58
|
+
debug: jest.fn(),
|
|
59
|
+
info: jest.fn(),
|
|
60
|
+
error: jest.fn()
|
|
61
|
+
};
|
|
62
|
+
mockConfig = {
|
|
63
|
+
enabled: true,
|
|
64
|
+
debug: false,
|
|
65
|
+
log: false,
|
|
66
|
+
prime: false,
|
|
67
|
+
endpoint: '$dist'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
mockGetConfig.mockReturnValue(mockConfig);
|
|
71
|
+
mockCreateLogger.mockReturnValue(mockLogger);
|
|
72
|
+
mockIsAdminUser.mockReturnValue(true);
|
|
73
|
+
mockGetVersionData.mockReturnValue({ Version: '2.9.0', RancherPrime: 'false' });
|
|
74
|
+
mockSystemInfoProvider.mockImplementation(() => ({ buildQueryString: () => 'qs=1' }));
|
|
75
|
+
|
|
76
|
+
// Mock localStorage
|
|
77
|
+
let store: { [key: string]: string } = {};
|
|
78
|
+
|
|
79
|
+
localStorageMock = {
|
|
80
|
+
getItem: (key: string) => store[key] || null,
|
|
81
|
+
setItem: (key: string, value: string) => {
|
|
82
|
+
store[key] = value.toString();
|
|
83
|
+
},
|
|
84
|
+
removeItem: (key: string) => {
|
|
85
|
+
delete store[key];
|
|
86
|
+
},
|
|
87
|
+
clear: () => {
|
|
88
|
+
store = {};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
|
|
92
|
+
|
|
93
|
+
jest.useFakeTimers();
|
|
94
|
+
|
|
95
|
+
// Mock console
|
|
96
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
jest.clearAllMocks();
|
|
101
|
+
jest.useRealTimers();
|
|
102
|
+
localStorageMock.clear();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('fetchAndProcessDynamicContent', () => {
|
|
106
|
+
it('should exit early if in single product mode', async() => {
|
|
107
|
+
mockGetters.isSingleProduct = true;
|
|
108
|
+
|
|
109
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
110
|
+
|
|
111
|
+
jest.runAllTimers();
|
|
112
|
+
|
|
113
|
+
await ret;
|
|
114
|
+
expect(mockGetConfig).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should exit early if dynamic content is disabled', async() => {
|
|
118
|
+
mockConfig.enabled = false;
|
|
119
|
+
await fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
120
|
+
|
|
121
|
+
expect(mockLogger.debug).not.toHaveBeenCalled();
|
|
122
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Dynamic content disabled through configuration'); // eslint-disable-line no-console
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should exit early if version is invalid', async() => {
|
|
126
|
+
mockGetVersionData.mockReturnValue({ Version: 'invalid' });
|
|
127
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
128
|
+
|
|
129
|
+
jest.runAllTimers();
|
|
130
|
+
|
|
131
|
+
await ret;
|
|
132
|
+
expect(mockProcessReleaseVersion).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should process content for community users', async() => {
|
|
136
|
+
const content = { releases: [], support: {} };
|
|
137
|
+
|
|
138
|
+
mockAxios.mockResolvedValue({ data: 'yaml: data' });
|
|
139
|
+
mockYamlLoad.mockReturnValue(content);
|
|
140
|
+
|
|
141
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
142
|
+
|
|
143
|
+
jest.runAllTimers();
|
|
144
|
+
await ret;
|
|
145
|
+
|
|
146
|
+
expect(mockProcessReleaseVersion).toHaveBeenCalledTimes(1);
|
|
147
|
+
expect(mockProcessSupportNotices).toHaveBeenCalledTimes(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should process content for prime admin users', async() => {
|
|
151
|
+
mockConfig.prime = true;
|
|
152
|
+
mockIsAdminUser.mockReturnValue(true);
|
|
153
|
+
const content = { releases: [], support: {} };
|
|
154
|
+
|
|
155
|
+
mockAxios.mockResolvedValue({ data: 'yaml: data' });
|
|
156
|
+
mockYamlLoad.mockReturnValue(content);
|
|
157
|
+
|
|
158
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
159
|
+
|
|
160
|
+
jest.runAllTimers();
|
|
161
|
+
await ret;
|
|
162
|
+
|
|
163
|
+
expect(mockProcessReleaseVersion).toHaveBeenCalledTimes(1);
|
|
164
|
+
expect(mockProcessSupportNotices).toHaveBeenCalledTimes(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should NOT process content for prime non-admin users', async() => {
|
|
168
|
+
mockConfig.prime = true;
|
|
169
|
+
mockIsAdminUser.mockReturnValue(false);
|
|
170
|
+
const content = { releases: [], support: {} };
|
|
171
|
+
|
|
172
|
+
mockAxios.mockResolvedValue({ data: 'yaml: data' });
|
|
173
|
+
mockYamlLoad.mockReturnValue(content);
|
|
174
|
+
|
|
175
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
176
|
+
|
|
177
|
+
jest.runAllTimers();
|
|
178
|
+
await ret;
|
|
179
|
+
|
|
180
|
+
expect(mockProcessReleaseVersion).not.toHaveBeenCalled();
|
|
181
|
+
expect(mockProcessSupportNotices).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should override version if debugVersion is set', async() => {
|
|
185
|
+
const content = { settings: { debugVersion: '2.10.0' } };
|
|
186
|
+
|
|
187
|
+
mockAxios.mockResolvedValue({ data: content });
|
|
188
|
+
mockYamlLoad.mockReturnValue(content);
|
|
189
|
+
|
|
190
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
191
|
+
|
|
192
|
+
jest.runAllTimers();
|
|
193
|
+
await ret;
|
|
194
|
+
|
|
195
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('Read configuration', {
|
|
196
|
+
debug: false, enabled: true, endpoint: '$dist', log: false, prime: false
|
|
197
|
+
});
|
|
198
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('Overriding version number to 2.10.0');
|
|
199
|
+
expect(mockProcessReleaseVersion).toHaveBeenCalledWith(
|
|
200
|
+
expect.any(Object),
|
|
201
|
+
undefined,
|
|
202
|
+
expect.objectContaining({ version: expect.objectContaining({ version: '2.10.0' }) })
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should clear logs if logging is disabled', async() => {
|
|
207
|
+
mockConfig.log = false;
|
|
208
|
+
localStorageMock.setItem('rancher-updates-debug-log', '[]');
|
|
209
|
+
mockAxios.mockResolvedValue({ data: '' });
|
|
210
|
+
|
|
211
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
212
|
+
|
|
213
|
+
jest.runAllTimers();
|
|
214
|
+
await ret;
|
|
215
|
+
|
|
216
|
+
expect(localStorageMock.getItem('rancher-updates-debug-log')).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should handle errors during processing', async() => {
|
|
220
|
+
const error = new Error('Processing failed');
|
|
221
|
+
|
|
222
|
+
mockProcessReleaseVersion.mockImplementation(() => {
|
|
223
|
+
throw error;
|
|
224
|
+
});
|
|
225
|
+
mockAxios.mockResolvedValue({ data: '' });
|
|
226
|
+
|
|
227
|
+
const ret = fetchAndProcessDynamicContent(mockDispatch, mockGetters, mockAxios);
|
|
228
|
+
|
|
229
|
+
jest.runAllTimers();
|
|
230
|
+
await ret;
|
|
231
|
+
|
|
232
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Error reading or processing dynamic content', error);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('fetchDynamicContent', () => {
|
|
237
|
+
let context: Context;
|
|
238
|
+
|
|
239
|
+
beforeEach(() => {
|
|
240
|
+
context = {
|
|
241
|
+
dispatch: mockDispatch,
|
|
242
|
+
getters: mockGetters,
|
|
243
|
+
axios: mockAxios,
|
|
244
|
+
logger: mockLogger,
|
|
245
|
+
config: mockConfig,
|
|
246
|
+
isAdmin: true,
|
|
247
|
+
settings: { releaseNotesUrl: '', suseExtensions: [] },
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should fetch and parse content when needed', async() => {
|
|
252
|
+
const content = { version: '1.0' };
|
|
253
|
+
|
|
254
|
+
mockAxios.mockResolvedValue({ data: 'yaml: data' });
|
|
255
|
+
mockYamlLoad.mockReturnValue(content);
|
|
256
|
+
|
|
257
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
258
|
+
|
|
259
|
+
jest.runAllTimers();
|
|
260
|
+
const result = await fetchPromise;
|
|
261
|
+
|
|
262
|
+
expect(mockAxios).toHaveBeenCalledTimes(1);
|
|
263
|
+
expect(mockYamlLoad).toHaveBeenCalledWith('yaml: data');
|
|
264
|
+
expect(result).toStrictEqual(content);
|
|
265
|
+
|
|
266
|
+
const tomorrow = dayjs().add(1, 'day').format(UPDATE_DATE_FORMAT);
|
|
267
|
+
|
|
268
|
+
expect(localStorageMock.getItem('rancher-updates-last-content')).toBe(JSON.stringify(content));
|
|
269
|
+
// Check updateFetchInfo(false) was called
|
|
270
|
+
expect(localStorageMock.getItem('rancher-updates-fetch-next')).toBe(tomorrow);
|
|
271
|
+
expect(localStorageMock.getItem('rancher-updates-fetch-errors')).toBeNull();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should skip fetch if not due', async() => {
|
|
275
|
+
const today = dayjs().format(UPDATE_DATE_FORMAT);
|
|
276
|
+
const tomorrow = dayjs().add(1, 'day').format(UPDATE_DATE_FORMAT);
|
|
277
|
+
|
|
278
|
+
localStorageMock.setItem('rancher-updates-fetch-next', tomorrow);
|
|
279
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
280
|
+
|
|
281
|
+
jest.runAllTimers();
|
|
282
|
+
await fetchPromise;
|
|
283
|
+
|
|
284
|
+
expect(mockAxios).not.toHaveBeenCalled();
|
|
285
|
+
expect(mockLogger.info).toHaveBeenCalledWith(`Skipping update check for dynamic content - next check due on ${ tomorrow } (today is ${ today })`);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should skip fetch if another is in progress', async() => {
|
|
289
|
+
const today = dayjs().format(UPDATE_DATE_FORMAT);
|
|
290
|
+
const later = dayjs().subtract(10, 'second').toString();
|
|
291
|
+
|
|
292
|
+
localStorageMock.setItem('rancher-updates-fetch-next', today);
|
|
293
|
+
localStorageMock.setItem('rancher-updates-fetching', later);
|
|
294
|
+
|
|
295
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
296
|
+
|
|
297
|
+
jest.runAllTimers();
|
|
298
|
+
await fetchPromise;
|
|
299
|
+
|
|
300
|
+
expect(mockAxios).not.toHaveBeenCalled();
|
|
301
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('Already fetching dynamic content in another tab (or previous tab closed while fetching) - skipping');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should proceed with fetch if a stale fetch was in progress', async() => {
|
|
305
|
+
(mockDayInstance.diff as jest.Mock).mockReturnValue(100); // more than 30s
|
|
306
|
+
localStorageMock.setItem('rancher-updates-fetching', new Date('2023-01-01T11:00:00Z').toString());
|
|
307
|
+
mockAxios.mockResolvedValue({ data: '' });
|
|
308
|
+
|
|
309
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
310
|
+
|
|
311
|
+
jest.runAllTimers();
|
|
312
|
+
await fetchPromise;
|
|
313
|
+
|
|
314
|
+
expect(mockAxios).toHaveBeenCalledTimes(1);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should handle axios fetch error and backoff', async() => {
|
|
318
|
+
const error = new Error('Network Error');
|
|
319
|
+
|
|
320
|
+
mockAxios.mockRejectedValue(error);
|
|
321
|
+
|
|
322
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
323
|
+
|
|
324
|
+
jest.runAllTimers();
|
|
325
|
+
await fetchPromise;
|
|
326
|
+
|
|
327
|
+
const tomorrow = dayjs().add(1, 'day').format(UPDATE_DATE_FORMAT);
|
|
328
|
+
|
|
329
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Error occurred reading dynamic content', error);
|
|
330
|
+
// Check updateFetchInfo(true) was called
|
|
331
|
+
expect(localStorageMock.getItem('rancher-updates-fetch-errors')).toBe('1');
|
|
332
|
+
expect(localStorageMock.getItem('rancher-updates-fetch-next')).toBe(tomorrow); // 1 day backoff
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should handle YAML parsing error', async() => {
|
|
336
|
+
const error = new Error('YAML Error');
|
|
337
|
+
|
|
338
|
+
mockAxios.mockResolvedValue({ data: 'invalid yaml' });
|
|
339
|
+
mockYamlLoad.mockImplementation(() => {
|
|
340
|
+
throw error;
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
344
|
+
|
|
345
|
+
jest.runAllTimers();
|
|
346
|
+
await fetchPromise;
|
|
347
|
+
|
|
348
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Failed to parse YAML/JSON from dynamic content package', error);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should handle axios fetch error with unexpected data', async() => {
|
|
352
|
+
mockAxios.mockResolvedValue({ data: null });
|
|
353
|
+
|
|
354
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
355
|
+
|
|
356
|
+
jest.runAllTimers();
|
|
357
|
+
await fetchPromise;
|
|
358
|
+
|
|
359
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Error fetching dynamic content package (unexpected data)');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should increment backoff on multiple consecutive errors', async() => {
|
|
363
|
+
const error = new Error('Network Error');
|
|
364
|
+
|
|
365
|
+
mockAxios.mockRejectedValue(error);
|
|
366
|
+
localStorageMock.setItem('rancher-updates-fetch-errors', '2'); // 2 previous errors
|
|
367
|
+
|
|
368
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
369
|
+
|
|
370
|
+
jest.runAllTimers();
|
|
371
|
+
await fetchPromise;
|
|
372
|
+
|
|
373
|
+
expect(localStorageMock.getItem('rancher-updates-fetch-errors')).toBe('3');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should cap backoff at max value', async() => {
|
|
377
|
+
const error = new Error('Network Error');
|
|
378
|
+
|
|
379
|
+
mockAxios.mockRejectedValue(error);
|
|
380
|
+
localStorageMock.setItem('rancher-updates-fetch-errors', '10');
|
|
381
|
+
|
|
382
|
+
const fetchPromise = fetchDynamicContent(context);
|
|
383
|
+
|
|
384
|
+
jest.runAllTimers();
|
|
385
|
+
await fetchPromise;
|
|
386
|
+
|
|
387
|
+
expect(localStorageMock.getItem('rancher-updates-fetch-errors')).toBe('6');
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
});
|