@northern.tech/store 0.1.0
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/.eslintrc.cjs +9 -0
- package/.prettierignore +1 -0
- package/CHANGELOG.md +12 -0
- package/actions.ts +32 -0
- package/api/general-api.test.ts +51 -0
- package/api/general-api.ts +60 -0
- package/api/types/AWSCredentials.ts +16 -0
- package/api/types/Actor.ts +35 -0
- package/api/types/Addon.ts +17 -0
- package/api/types/AddonTenantadm.ts +17 -0
- package/api/types/Alert.ts +34 -0
- package/api/types/AlertDetails.ts +23 -0
- package/api/types/AlertSubject.ts +23 -0
- package/api/types/ApiBurst.ts +21 -0
- package/api/types/ApiBurstTenantadm.ts +21 -0
- package/api/types/ApiLimits.ts +16 -0
- package/api/types/ApiLimitsTenantadm.ts +16 -0
- package/api/types/ApiQuota.ts +17 -0
- package/api/types/ApiQuotaTenantadm.ts +17 -0
- package/api/types/Artifact.ts +48 -0
- package/api/types/ArtifactDeployments.ts +48 -0
- package/api/types/ArtifactInfo.ts +11 -0
- package/api/types/ArtifactInfoDeployments.ts +11 -0
- package/api/types/ArtifactLink.ts +11 -0
- package/api/types/ArtifactTypeInfo.ts +13 -0
- package/api/types/ArtifactTypeInfoDeployments.ts +13 -0
- package/api/types/ArtifactUpdate.ts +10 -0
- package/api/types/ArtifactUploadLink.ts +15 -0
- package/api/types/Attribute.ts +36 -0
- package/api/types/AttributeInventory.ts +32 -0
- package/api/types/AuditLog.ts +26 -0
- package/api/types/AuthSet.ts +33 -0
- package/api/types/AuthSetDeviceauth.ts +37 -0
- package/api/types/AutoAuthRequest.ts +23 -0
- package/api/types/AzureSharedAccessSecret.ts +11 -0
- package/api/types/BillingInfo.ts +13 -0
- package/api/types/BinaryDeltaConfiguration.ts +15 -0
- package/api/types/BinaryDeltaConfigurationDeployments.ts +15 -0
- package/api/types/BinaryDeltaLimits.ts +15 -0
- package/api/types/BinaryDeltaLimitsDeployments.ts +15 -0
- package/api/types/BoundingBox.ts +11 -0
- package/api/types/CancelRequest.ts +10 -0
- package/api/types/CardData.ts +49 -0
- package/api/types/CardSetupData.ts +17 -0
- package/api/types/CheckoutData.ts +13 -0
- package/api/types/Configuration.ts +11 -0
- package/api/types/Count.ts +13 -0
- package/api/types/Credentials.ts +23 -0
- package/api/types/DBusSubsystem.ts +27 -0
- package/api/types/DeltaConfiguration.ts +14 -0
- package/api/types/DeltaConfigurationDeployments.ts +14 -0
- package/api/types/Deployment.ts +95 -0
- package/api/types/DeploymentAggregation.ts +18 -0
- package/api/types/DeploymentAggregationItem.ts +18 -0
- package/api/types/DeploymentAggregationTerm.ts +23 -0
- package/api/types/DeploymentAggregationTerms.ts +18 -0
- package/api/types/DeploymentAttributeProjection.ts +11 -0
- package/api/types/DeploymentAuditlogs.ts +11 -0
- package/api/types/DeploymentDeployments.ts +99 -0
- package/api/types/DeploymentFilterTerm.ts +37 -0
- package/api/types/DeploymentIdentifier.ts +10 -0
- package/api/types/DeploymentPhase.ts +23 -0
- package/api/types/DeploymentReporting.ts +46 -0
- package/api/types/DeploymentSearchTerms.ts +39 -0
- package/api/types/DeploymentSortTerm.ts +25 -0
- package/api/types/DeploymentStatistics.ts +15 -0
- package/api/types/DeploymentStatusStatistics.ts +54 -0
- package/api/types/Device.ts +29 -0
- package/api/types/DeviceAggregation.ts +18 -0
- package/api/types/DeviceAggregationItem.ts +18 -0
- package/api/types/DeviceAggregationTerm.ts +27 -0
- package/api/types/DeviceAggregationTerms.ts +22 -0
- package/api/types/DeviceAttribute.ts +23 -0
- package/api/types/DeviceAttributeProjection.ts +15 -0
- package/api/types/DeviceAuditlogs.ts +17 -0
- package/api/types/DeviceAuthEvent.ts +23 -0
- package/api/types/DeviceConfiguration.ts +16 -0
- package/api/types/DeviceDeployment.ts +11 -0
- package/api/types/DeviceDeployments.ts +31 -0
- package/api/types/DeviceDeviceauth.ts +42 -0
- package/api/types/DeviceFilterAttribute.ts +22 -0
- package/api/types/DeviceFilterTerm.ts +41 -0
- package/api/types/DeviceInventory.ts +19 -0
- package/api/types/DeviceInventoryInventory.ts +19 -0
- package/api/types/DeviceReporting.ts +22 -0
- package/api/types/DeviceSearchTerms.ts +39 -0
- package/api/types/DeviceSortTerm.ts +29 -0
- package/api/types/DeviceState.ts +32 -0
- package/api/types/DeviceStateDeviceconnect.ts +33 -0
- package/api/types/DeviceStateIot_manager.ts +14 -0
- package/api/types/DeviceStatus.ts +19 -0
- package/api/types/DeviceWithImage.ts +80 -0
- package/api/types/DirectUploadMetadata.ts +18 -0
- package/api/types/EmailVerificationCompletion.ts +13 -0
- package/api/types/EmailVerificationRequest.ts +13 -0
- package/api/types/Error.ts +17 -0
- package/api/types/ErrorAuditlogs.ts +13 -0
- package/api/types/ErrorDeployments.ts +17 -0
- package/api/types/ErrorDeviceauth.ts +13 -0
- package/api/types/ErrorDeviceconfig.ts +17 -0
- package/api/types/ErrorDeviceconnect.ts +17 -0
- package/api/types/ErrorDevicemonitor.ts +17 -0
- package/api/types/ErrorExt.ts +18 -0
- package/api/types/ErrorInventory.ts +17 -0
- package/api/types/ErrorIot_manager.ts +17 -0
- package/api/types/ErrorNotFound.ts +17 -0
- package/api/types/ErrorReporting.ts +18 -0
- package/api/types/ErrorTenantadm.ts +13 -0
- package/api/types/ErrorUseradm.ts +17 -0
- package/api/types/Event.ts +48 -0
- package/api/types/ExternalIdentity.ts +17 -0
- package/api/types/Features.ts +17 -0
- package/api/types/FileUpload.ts +23 -0
- package/api/types/Filter.ts +19 -0
- package/api/types/FilterAttribute.ts +21 -0
- package/api/types/FilterDefinition.ts +18 -0
- package/api/types/FilterDeployments.ts +19 -0
- package/api/types/FilterInventory.ts +20 -0
- package/api/types/FilterPredicate.ts +78 -0
- package/api/types/FilterPredicateDeployments.ts +48 -0
- package/api/types/FilterPredicateInventory.ts +76 -0
- package/api/types/GeoBoundingBox.ts +10 -0
- package/api/types/GeoBoundingBoxFilter.ts +6 -0
- package/api/types/GeoDistance.ts +17 -0
- package/api/types/GeoDistanceFilter.ts +10 -0
- package/api/types/GeoPoint.ts +15 -0
- package/api/types/Group.ts +10 -0
- package/api/types/HTTP.ts +19 -0
- package/api/types/IdentityData.ts +25 -0
- package/api/types/InputParameter.ts +8 -0
- package/api/types/Integration.ts +24 -0
- package/api/types/JobStatus.ts +13 -0
- package/api/types/Limit.ts +21 -0
- package/api/types/LimitDeployments.ts +21 -0
- package/api/types/LimitDeviceauth.ts +10 -0
- package/api/types/LimitUseradm.ts +11 -0
- package/api/types/Limits.ts +11 -0
- package/api/types/LineDescriptor.ts +11 -0
- package/api/types/LogSubsystem.ts +23 -0
- package/api/types/LoginOptions.ts +18 -0
- package/api/types/ManagementAPIConfiguration.ts +5 -0
- package/api/types/MenderTypes.ts +183 -0
- package/api/types/MonitorConfiguration.ts +22 -0
- package/api/types/NewConfigurationDeployment.ts +15 -0
- package/api/types/NewConfigurationDeploymentResponse.ts +10 -0
- package/api/types/NewDeployment.ts +42 -0
- package/api/types/NewDeploymentForGroup.ts +33 -0
- package/api/types/NewDeploymentPhase.ts +37 -0
- package/api/types/NewDeploymentPhaseDeployments.ts +20 -0
- package/api/types/NewDeploymentV2.ts +31 -0
- package/api/types/NewTenant.ts +41 -0
- package/api/types/Object.ts +35 -0
- package/api/types/OwnUserUpdate.ts +21 -0
- package/api/types/PasswordResetCompletion.ts +17 -0
- package/api/types/PasswordResetRequest.ts +13 -0
- package/api/types/Permission.ts +21 -0
- package/api/types/PermissionObject.ts +14 -0
- package/api/types/PermissionSet.ts +22 -0
- package/api/types/PermissionSetWithScope.ts +15 -0
- package/api/types/PersonalAccessToken.ts +29 -0
- package/api/types/PersonalAccessTokenRequest.ts +18 -0
- package/api/types/Plan.ts +19 -0
- package/api/types/PlanBindingDetails.ts +13 -0
- package/api/types/PlanChangeRequest.ts +14 -0
- package/api/types/PlanLimits.ts +12 -0
- package/api/types/PreAuthSet.ts +16 -0
- package/api/types/Release.ts +32 -0
- package/api/types/ReleaseDeployments.ts +32 -0
- package/api/types/ReleaseUpdate.ts +13 -0
- package/api/types/Releases.ts +9 -0
- package/api/types/ReleasesDeleteError.ts +21 -0
- package/api/types/ReleasesDeployments.ts +9 -0
- package/api/types/Role.ts +19 -0
- package/api/types/RolePermission.ts +25 -0
- package/api/types/RolePermissionObject.ts +17 -0
- package/api/types/RoleUseradm.ts +19 -0
- package/api/types/SAMLMetadata.ts +21 -0
- package/api/types/Scope.ts +19 -0
- package/api/types/SelectAttribute.ts +17 -0
- package/api/types/ServiceSubsystem.ts +19 -0
- package/api/types/Settings.ts +23 -0
- package/api/types/SortCriteria.ts +30 -0
- package/api/types/SsoObject.ts +21 -0
- package/api/types/Status.ts +17 -0
- package/api/types/StatusDeviceauth.ts +18 -0
- package/api/types/StatusTenantadm.ts +19 -0
- package/api/types/StorageLimit.ts +17 -0
- package/api/types/SubscriptionData.ts +8 -0
- package/api/types/SupportRequest.ts +17 -0
- package/api/types/Tag.ts +27 -0
- package/api/types/Tags.ts +10 -0
- package/api/types/TaskResult.ts +13 -0
- package/api/types/TaskResultCLI.ts +10 -0
- package/api/types/TaskResultHTTPRequest.ts +8 -0
- package/api/types/TaskResultHTTPResponse.ts +10 -0
- package/api/types/Tenant.ts +38 -0
- package/api/types/TenantApiLimits.ts +12 -0
- package/api/types/TenantApiLimitsTenantadm.ts +12 -0
- package/api/types/TenantIdName.ts +14 -0
- package/api/types/TenantIdsData.ts +10 -0
- package/api/types/TenantInfo.ts +14 -0
- package/api/types/TenantShortInfo.ts +21 -0
- package/api/types/TenantTenantadm.ts +38 -0
- package/api/types/TenantsIdName.ts +6 -0
- package/api/types/Update.ts +17 -0
- package/api/types/UpdateDeployments.ts +18 -0
- package/api/types/UpdateFile.ts +13 -0
- package/api/types/UpdateFileDeployments.ts +13 -0
- package/api/types/UpdateTypes.ts +8 -0
- package/api/types/UpgradeCompleteRequest.ts +23 -0
- package/api/types/User.ts +13 -0
- package/api/types/UserAuditlogs.ts +13 -0
- package/api/types/UserNew.ts +26 -0
- package/api/types/UserUpdate.ts +25 -0
- package/api/types/UserUseradm.ts +45 -0
- package/api/types/UserWithTenantInfo.ts +8 -0
- package/api/types/XDeltaArgs.ts +9 -0
- package/api/types/XDeltaArgsDeployments.ts +9 -0
- package/api/types/XDeltaArgsLimits.ts +10 -0
- package/api/types/XDeltaArgsLimitsDeployments.ts +10 -0
- package/api/users-api.ts +34 -0
- package/appSlice/constants.ts +166 -0
- package/appSlice/index.ts +159 -0
- package/appSlice/reducer.test.ts +97 -0
- package/appSlice/selectors.ts +33 -0
- package/appSlice/thunks.test.ts +118 -0
- package/appSlice/thunks.ts +148 -0
- package/auth.test.ts +36 -0
- package/auth.ts +82 -0
- package/commonConstants.tsx +252 -0
- package/commonSelectors.ts +255 -0
- package/constants.ts +22 -0
- package/deploymentsSlice/constants.ts +175 -0
- package/deploymentsSlice/index.ts +145 -0
- package/deploymentsSlice/reducer.test.ts +108 -0
- package/deploymentsSlice/selectors.ts +58 -0
- package/deploymentsSlice/thunks.test.ts +425 -0
- package/deploymentsSlice/thunks.ts +382 -0
- package/devicesSlice/constants.ts +56 -0
- package/devicesSlice/index.ts +207 -0
- package/devicesSlice/reducer.test.ts +194 -0
- package/devicesSlice/selectors.ts +127 -0
- package/devicesSlice/thunks.test.tsx +1245 -0
- package/devicesSlice/thunks.tsx +1098 -0
- package/monitorSlice/constants.ts +17 -0
- package/monitorSlice/index.ts +65 -0
- package/monitorSlice/reducer.test.ts +79 -0
- package/monitorSlice/selectors.ts +15 -0
- package/monitorSlice/thunks.test.ts +112 -0
- package/monitorSlice/thunks.ts +101 -0
- package/onboardingSlice/constants.ts +53 -0
- package/onboardingSlice/index.ts +60 -0
- package/onboardingSlice/reducer.test.ts +45 -0
- package/onboardingSlice/selectors.ts +15 -0
- package/onboardingSlice/thunks.test.ts +235 -0
- package/onboardingSlice/thunks.ts +142 -0
- package/organizationSlice/constants.ts +82 -0
- package/organizationSlice/index.ts +111 -0
- package/organizationSlice/reducer.test.ts +78 -0
- package/organizationSlice/selectors.ts +45 -0
- package/organizationSlice/thunks.test.ts +619 -0
- package/organizationSlice/thunks.ts +383 -0
- package/package.json +38 -0
- package/releasesSlice/constants.ts +18 -0
- package/releasesSlice/index.ts +116 -0
- package/releasesSlice/reducer.test.ts +78 -0
- package/releasesSlice/selectors.ts +33 -0
- package/releasesSlice/thunks.test.ts +389 -0
- package/releasesSlice/thunks.ts +379 -0
- package/selectors.ts +22 -0
- package/store.ts +89 -0
- package/storehooks.test.tsx +386 -0
- package/storehooks.ts +250 -0
- package/thunks.ts +21 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +11 -0
- package/usersSlice/constants.ts +374 -0
- package/usersSlice/index.ts +151 -0
- package/usersSlice/reducer.test.ts +141 -0
- package/usersSlice/selectors.ts +60 -0
- package/usersSlice/thunks.test.ts +660 -0
- package/usersSlice/thunks.ts +773 -0
- package/utils.test.ts +187 -0
- package/utils.ts +249 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
// Copyright 2020 Northern.tech AS
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
// @ts-nocheck
|
|
15
|
+
import { getSessionInfo } from '@northern.tech/store/auth';
|
|
16
|
+
import { emptyRole } from '@northern.tech/store/commonConstants';
|
|
17
|
+
import { setOfflineThreshold } from '@northern.tech/store/thunks';
|
|
18
|
+
import { act } from '@testing-library/react';
|
|
19
|
+
import configureMockStore from 'redux-mock-store';
|
|
20
|
+
import { thunk } from 'redux-thunk';
|
|
21
|
+
import Cookies from 'universal-cookie';
|
|
22
|
+
|
|
23
|
+
import { actions } from '.';
|
|
24
|
+
import { accessTokens, defaultPassword, defaultState, receivedPermissionSets, receivedRoles, testSsoId, userId } from '../../../../tests/mockData';
|
|
25
|
+
import { HELPTOOLTIPS } from '../../components/helptips/helptooltips';
|
|
26
|
+
import { actions as appActions } from '../appSlice';
|
|
27
|
+
import { getSamlStartUrl } from '../organizationSlice/constants';
|
|
28
|
+
import { USER_LOGOUT, uiPermissionsById } from './constants';
|
|
29
|
+
import {
|
|
30
|
+
addUserToCurrentTenant,
|
|
31
|
+
createRole,
|
|
32
|
+
createUser,
|
|
33
|
+
disableUser2fa,
|
|
34
|
+
editRole,
|
|
35
|
+
editUser,
|
|
36
|
+
enableUser2fa,
|
|
37
|
+
generateToken,
|
|
38
|
+
get2FAQRCode,
|
|
39
|
+
getGlobalSettings,
|
|
40
|
+
getPermissionSets,
|
|
41
|
+
getRoles,
|
|
42
|
+
getTokens,
|
|
43
|
+
getUser,
|
|
44
|
+
getUserList,
|
|
45
|
+
getUserSettings,
|
|
46
|
+
initializeSelf,
|
|
47
|
+
loginUser,
|
|
48
|
+
logoutUser,
|
|
49
|
+
passwordResetComplete,
|
|
50
|
+
passwordResetStart,
|
|
51
|
+
removeRole,
|
|
52
|
+
removeUser,
|
|
53
|
+
revokeToken,
|
|
54
|
+
saveGlobalSettings,
|
|
55
|
+
saveUserSettings,
|
|
56
|
+
setAllTooltipsReadState,
|
|
57
|
+
setHideAnnouncement,
|
|
58
|
+
setTooltipReadState,
|
|
59
|
+
switchUserOrganization,
|
|
60
|
+
updateUserColumnSettings,
|
|
61
|
+
verify2FA,
|
|
62
|
+
verifyEmailComplete,
|
|
63
|
+
verifyEmailStart
|
|
64
|
+
} from './thunks';
|
|
65
|
+
|
|
66
|
+
const middlewares = [thunk];
|
|
67
|
+
const mockStore = configureMockStore(middlewares);
|
|
68
|
+
|
|
69
|
+
const defaultRole = { ...emptyRole, name: 'test', description: 'test description' };
|
|
70
|
+
const settings = { test: true };
|
|
71
|
+
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
73
|
+
const { attributes, ...expectedDevice } = defaultState.devices.byId.a1;
|
|
74
|
+
|
|
75
|
+
export const offlineThreshold = [
|
|
76
|
+
{ type: setOfflineThreshold.pending.type },
|
|
77
|
+
{ type: appActions.setOfflineThreshold.type, payload: '2019-01-12T13:00:00.900Z' },
|
|
78
|
+
{ type: setOfflineThreshold.fulfilled.type }
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/* eslint-disable sonarjs/no-identical-functions */
|
|
82
|
+
describe('user actions', () => {
|
|
83
|
+
it('should allow retrieving 2fa qr codes', async () => {
|
|
84
|
+
jest.clearAllMocks();
|
|
85
|
+
const expectedActions = [
|
|
86
|
+
{ type: get2FAQRCode.pending.type },
|
|
87
|
+
{ type: actions.receivedQrCode.type, payload: btoa('test') },
|
|
88
|
+
{ type: get2FAQRCode.fulfilled.type }
|
|
89
|
+
];
|
|
90
|
+
const store = mockStore({ ...defaultState });
|
|
91
|
+
await store.dispatch(get2FAQRCode(true));
|
|
92
|
+
const storeActions = store.getActions();
|
|
93
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
94
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const commonUserRetrievalActions = [
|
|
98
|
+
{ type: setHideAnnouncement.pending.type },
|
|
99
|
+
{ type: updateUserColumnSettings.pending.type },
|
|
100
|
+
{ type: actions.setCustomColumns.type, payload: [] },
|
|
101
|
+
{ type: setHideAnnouncement.fulfilled.type },
|
|
102
|
+
{ type: updateUserColumnSettings.fulfilled.type },
|
|
103
|
+
{ type: getUser.fulfilled.type }
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
it('should verify 2fa codes during 2fa setup', async () => {
|
|
107
|
+
jest.clearAllMocks();
|
|
108
|
+
const expectedActions = [
|
|
109
|
+
{ type: verify2FA.pending.type },
|
|
110
|
+
{ type: getUser.pending.type },
|
|
111
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId[userId] },
|
|
112
|
+
...commonUserRetrievalActions,
|
|
113
|
+
{ type: verify2FA.fulfilled.type }
|
|
114
|
+
];
|
|
115
|
+
const store = mockStore({ ...defaultState });
|
|
116
|
+
await store.dispatch(verify2FA({ token2fa: '123456' }));
|
|
117
|
+
const storeActions = store.getActions();
|
|
118
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
119
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
120
|
+
});
|
|
121
|
+
it('should allow enabling 2fa during 2fa setup', async () => {
|
|
122
|
+
jest.clearAllMocks();
|
|
123
|
+
const expectedActions = [
|
|
124
|
+
{ type: enableUser2fa.pending.type },
|
|
125
|
+
{ type: getUser.pending.type },
|
|
126
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId.a1 },
|
|
127
|
+
...commonUserRetrievalActions,
|
|
128
|
+
{ type: enableUser2fa.fulfilled.type }
|
|
129
|
+
];
|
|
130
|
+
const store = mockStore({ ...defaultState });
|
|
131
|
+
await store.dispatch(enableUser2fa(defaultState.users.byId.a1.id));
|
|
132
|
+
const storeActions = store.getActions();
|
|
133
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
134
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
135
|
+
});
|
|
136
|
+
it('should allow disabling 2fa during 2fa setup', async () => {
|
|
137
|
+
jest.clearAllMocks();
|
|
138
|
+
const expectedActions = [
|
|
139
|
+
{ type: disableUser2fa.pending.type },
|
|
140
|
+
{ type: getUser.pending.type },
|
|
141
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId.a1 },
|
|
142
|
+
...commonUserRetrievalActions,
|
|
143
|
+
{ type: disableUser2fa.fulfilled.type }
|
|
144
|
+
];
|
|
145
|
+
const store = mockStore({ ...defaultState });
|
|
146
|
+
await store.dispatch(disableUser2fa(defaultState.users.byId.a1.id));
|
|
147
|
+
const storeActions = store.getActions();
|
|
148
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
149
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
150
|
+
});
|
|
151
|
+
it('should allow beginning email verification', async () => {
|
|
152
|
+
jest.clearAllMocks();
|
|
153
|
+
const expectedActions = [
|
|
154
|
+
{ type: verifyEmailStart.pending.type },
|
|
155
|
+
{ type: getUser.pending.type },
|
|
156
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId[userId] },
|
|
157
|
+
...commonUserRetrievalActions,
|
|
158
|
+
{ type: verifyEmailStart.fulfilled.type }
|
|
159
|
+
];
|
|
160
|
+
const store = mockStore({ ...defaultState });
|
|
161
|
+
await store.dispatch(verifyEmailStart());
|
|
162
|
+
const storeActions = store.getActions();
|
|
163
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
164
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
165
|
+
});
|
|
166
|
+
it('should allow completing email verification', async () => {
|
|
167
|
+
jest.clearAllMocks();
|
|
168
|
+
const expectedActions = [
|
|
169
|
+
{ type: verifyEmailComplete.pending.type },
|
|
170
|
+
{ type: getUser.pending.type },
|
|
171
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId[userId] },
|
|
172
|
+
...commonUserRetrievalActions,
|
|
173
|
+
{ type: verifyEmailComplete.fulfilled.type }
|
|
174
|
+
];
|
|
175
|
+
const store = mockStore({ ...defaultState });
|
|
176
|
+
await store.dispatch(verifyEmailComplete('superSecret'));
|
|
177
|
+
const storeActions = store.getActions();
|
|
178
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
179
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
180
|
+
await expect(store.dispatch(verifyEmailComplete('ohNo')).unwrap()).rejects.toBeTruthy();
|
|
181
|
+
});
|
|
182
|
+
it('should allow logging in', async () => {
|
|
183
|
+
jest.clearAllMocks();
|
|
184
|
+
const expectedActions = [
|
|
185
|
+
{ type: loginUser.pending.type },
|
|
186
|
+
{ type: getUser.pending.type },
|
|
187
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId[userId] },
|
|
188
|
+
...commonUserRetrievalActions,
|
|
189
|
+
{ type: actions.successfullyLoggedIn.type, payload: getSessionInfo() },
|
|
190
|
+
{ type: loginUser.fulfilled.type }
|
|
191
|
+
];
|
|
192
|
+
const store = mockStore({ ...defaultState });
|
|
193
|
+
await store.dispatch(loginUser({ email: 'test@example.com', password: defaultPassword }));
|
|
194
|
+
const storeActions = store.getActions();
|
|
195
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
196
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
197
|
+
});
|
|
198
|
+
it('should redirect on SSO login', async () => {
|
|
199
|
+
const store = mockStore({ ...defaultState });
|
|
200
|
+
const replaceSpy = jest.spyOn(window.location, 'replace');
|
|
201
|
+
await store.dispatch(loginUser({ email: 'test@example.com' }));
|
|
202
|
+
await act(async () => {
|
|
203
|
+
jest.runOnlyPendingTimers();
|
|
204
|
+
jest.runAllTicks();
|
|
205
|
+
});
|
|
206
|
+
expect(replaceSpy).toHaveBeenCalledWith(getSamlStartUrl(testSsoId));
|
|
207
|
+
});
|
|
208
|
+
it('should prevent logging in with a limited user', async () => {
|
|
209
|
+
jest.clearAllMocks();
|
|
210
|
+
window.localStorage.getItem.mockReturnValueOnce(JSON.stringify({ token: 'limitedToken' }));
|
|
211
|
+
const expectedActions = [
|
|
212
|
+
{ type: loginUser.pending.type },
|
|
213
|
+
{ type: getUser.pending.type },
|
|
214
|
+
{ type: getUser.rejected.type },
|
|
215
|
+
{ type: appActions.setSnackbar.type, payload: 'forbidden by role-based access control' },
|
|
216
|
+
// {
|
|
217
|
+
// type: appActions.setSnackbar.type,
|
|
218
|
+
// payload: 'There was a problem logging in. Please check your email and password. If you still have problems, contact an administrator.'
|
|
219
|
+
// },
|
|
220
|
+
{ type: loginUser.rejected.type }
|
|
221
|
+
];
|
|
222
|
+
const store = mockStore({ ...defaultState });
|
|
223
|
+
try {
|
|
224
|
+
await store.dispatch(loginUser({ email: 'test-limited@example.com', password: defaultPassword }));
|
|
225
|
+
} catch (error) {
|
|
226
|
+
expect(error).toMatchObject(expectedActions[5]);
|
|
227
|
+
}
|
|
228
|
+
await act(async () => {
|
|
229
|
+
jest.runOnlyPendingTimers();
|
|
230
|
+
jest.runAllTicks();
|
|
231
|
+
});
|
|
232
|
+
expect(window.localStorage.removeItem).toHaveBeenCalledWith('JWT');
|
|
233
|
+
window.localStorage.getItem.mockReset();
|
|
234
|
+
const storeActions = store.getActions();
|
|
235
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
236
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
237
|
+
});
|
|
238
|
+
it('should allow logging out', async () => {
|
|
239
|
+
jest.clearAllMocks();
|
|
240
|
+
const expectedActions = [{ type: logoutUser.pending.type }, { type: USER_LOGOUT }, { type: logoutUser.fulfilled.type }];
|
|
241
|
+
const store = mockStore({ ...defaultState });
|
|
242
|
+
await store.dispatch(logoutUser());
|
|
243
|
+
const storeActions = store.getActions();
|
|
244
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
245
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
246
|
+
});
|
|
247
|
+
it('should not allow logging out with an active upload', async () => {
|
|
248
|
+
jest.clearAllMocks();
|
|
249
|
+
const store = mockStore({ ...defaultState, releases: { ...defaultState.releases, uploadProgress: 42 } });
|
|
250
|
+
await store.dispatch(logoutUser()).catch(() => expect(true).toEqual(true));
|
|
251
|
+
});
|
|
252
|
+
it('should allow switching users', async () => {
|
|
253
|
+
jest.clearAllMocks();
|
|
254
|
+
const reloadSpy = jest.spyOn(window.location, 'reload');
|
|
255
|
+
const store = mockStore({ ...defaultState, releases: { ...defaultState.releases, uploadProgress: 42 } });
|
|
256
|
+
await store.dispatch(switchUserOrganization('a1'));
|
|
257
|
+
expect(localStorage.getItem).toHaveBeenCalledWith('JWT');
|
|
258
|
+
expect(localStorage.setItem).toHaveBeenCalledWith('JWT', JSON.stringify({ token: 'differentToken' }));
|
|
259
|
+
expect(reloadSpy).toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
it('should not allow switching users during uploads', async () => {
|
|
262
|
+
jest.clearAllMocks();
|
|
263
|
+
const store = mockStore({ ...defaultState, releases: { ...defaultState.releases, uploadProgress: 42 } });
|
|
264
|
+
await store.dispatch(switchUserOrganization('a1')).catch(() => expect(true).toEqual(true));
|
|
265
|
+
});
|
|
266
|
+
it('should allow single user retrieval', async () => {
|
|
267
|
+
jest.clearAllMocks();
|
|
268
|
+
const expectedActions = [
|
|
269
|
+
{ type: getUser.pending.type },
|
|
270
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId.a1 },
|
|
271
|
+
...commonUserRetrievalActions
|
|
272
|
+
];
|
|
273
|
+
const store = mockStore({ ...defaultState });
|
|
274
|
+
await store.dispatch(getUser('a1'));
|
|
275
|
+
const storeActions = store.getActions();
|
|
276
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
277
|
+
// we can't check for the correct value here as the localstorage is (ab)used by msw to track state during req/res cycles, thus the localStorage expectation
|
|
278
|
+
expect(window.localStorage.getItem).toHaveBeenCalledWith(`a1-column-widths`);
|
|
279
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
280
|
+
});
|
|
281
|
+
it('should allow current user initialization', async () => {
|
|
282
|
+
jest.clearAllMocks();
|
|
283
|
+
const expectedActions = [
|
|
284
|
+
{ type: initializeSelf.pending.type },
|
|
285
|
+
{ type: getUser.pending.type },
|
|
286
|
+
{ type: actions.receivedUser.type, payload: defaultState.users.byId[userId] },
|
|
287
|
+
...commonUserRetrievalActions,
|
|
288
|
+
{ type: initializeSelf.fulfilled.type }
|
|
289
|
+
];
|
|
290
|
+
const store = mockStore({ ...defaultState });
|
|
291
|
+
await store.dispatch(initializeSelf());
|
|
292
|
+
const storeActions = store.getActions();
|
|
293
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
294
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
295
|
+
});
|
|
296
|
+
it('should allow user list retrieval', async () => {
|
|
297
|
+
jest.clearAllMocks();
|
|
298
|
+
const expectedActions = [
|
|
299
|
+
{ type: getUserList.pending.type },
|
|
300
|
+
{ type: actions.receivedUserList.type, payload: defaultState.users.byId },
|
|
301
|
+
{ type: getUserList.fulfilled.type }
|
|
302
|
+
];
|
|
303
|
+
const store = mockStore({ ...defaultState });
|
|
304
|
+
await store.dispatch(getUserList());
|
|
305
|
+
const storeActions = store.getActions();
|
|
306
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
307
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
308
|
+
});
|
|
309
|
+
it('should allow single user creation', async () => {
|
|
310
|
+
jest.clearAllMocks();
|
|
311
|
+
const createdUser = { email: 'a@b.com', password: defaultPassword };
|
|
312
|
+
const expectedActions = [
|
|
313
|
+
{ type: createUser.pending.type },
|
|
314
|
+
{ type: actions.createdUser.type, payload: createdUser },
|
|
315
|
+
{ type: getUserList.pending.type },
|
|
316
|
+
{ type: appActions.setSnackbar.type, payload: 'The user was created successfully.' },
|
|
317
|
+
{ type: actions.receivedUserList.type, payload: defaultState.users.byId },
|
|
318
|
+
{ type: getUserList.fulfilled.type },
|
|
319
|
+
{ type: createUser.fulfilled.type }
|
|
320
|
+
];
|
|
321
|
+
const store = mockStore({ ...defaultState });
|
|
322
|
+
await store.dispatch(createUser(createdUser));
|
|
323
|
+
const storeActions = store.getActions();
|
|
324
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
325
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
326
|
+
});
|
|
327
|
+
it('should allow single user edits', async () => {
|
|
328
|
+
jest.clearAllMocks();
|
|
329
|
+
const expectedActions = [
|
|
330
|
+
{ type: editUser.pending.type },
|
|
331
|
+
{ type: actions.updatedUser.type, payload: { id: 'a1', password: defaultPassword } },
|
|
332
|
+
{ type: appActions.setSnackbar.type, payload: 'The user has been updated.' },
|
|
333
|
+
{ type: editUser.fulfilled.type }
|
|
334
|
+
];
|
|
335
|
+
const store = mockStore({ ...defaultState });
|
|
336
|
+
await store.dispatch(editUser({ id: 'a1', email: defaultState.users.byId.a1.email, password: defaultPassword }));
|
|
337
|
+
const storeActions = store.getActions();
|
|
338
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
339
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
340
|
+
});
|
|
341
|
+
it('should not allow current user edits without proper password', async () => {
|
|
342
|
+
jest.clearAllMocks();
|
|
343
|
+
const store = mockStore({ ...defaultState });
|
|
344
|
+
await expect(store.dispatch(editUser({ id: 'a1', email: 'a@evil.com', password: 'mySecretPasswordNot' })).unwrap()).rejects.toBeTruthy();
|
|
345
|
+
});
|
|
346
|
+
it('should allow single user removal', async () => {
|
|
347
|
+
jest.clearAllMocks();
|
|
348
|
+
const expectedActions = [
|
|
349
|
+
{ type: removeUser.pending.type },
|
|
350
|
+
{ type: actions.removedUser.type, payload: 'a1' },
|
|
351
|
+
{ type: getUserList.pending.type },
|
|
352
|
+
{ type: appActions.setSnackbar.type, payload: 'The user was removed from the system.' },
|
|
353
|
+
{ type: actions.receivedUserList.type, payload: defaultState.users.byId },
|
|
354
|
+
{ type: getUserList.fulfilled.type },
|
|
355
|
+
{ type: removeUser.fulfilled.type }
|
|
356
|
+
];
|
|
357
|
+
const store = mockStore({ ...defaultState });
|
|
358
|
+
await store.dispatch(removeUser('a1'));
|
|
359
|
+
const storeActions = store.getActions();
|
|
360
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
361
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
362
|
+
});
|
|
363
|
+
it('should allow single user removal', async () => {
|
|
364
|
+
jest.clearAllMocks();
|
|
365
|
+
const expectedActions = [
|
|
366
|
+
{ type: addUserToCurrentTenant.pending.type },
|
|
367
|
+
{ type: appActions.setSnackbar.type, payload: 'The user was added successfully.' },
|
|
368
|
+
{ type: getUserList.pending.type },
|
|
369
|
+
{ type: actions.receivedUserList.type, payload: defaultState.users.byId },
|
|
370
|
+
{ type: getUserList.fulfilled.type },
|
|
371
|
+
{ type: addUserToCurrentTenant.fulfilled.type }
|
|
372
|
+
];
|
|
373
|
+
const store = mockStore({ ...defaultState });
|
|
374
|
+
await store.dispatch(addUserToCurrentTenant('a1'));
|
|
375
|
+
const storeActions = store.getActions();
|
|
376
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
377
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should allow role list retrieval', async () => {
|
|
381
|
+
jest.clearAllMocks();
|
|
382
|
+
const expectedActions = [
|
|
383
|
+
{ type: getRoles.pending.type },
|
|
384
|
+
{ type: getPermissionSets.pending.type },
|
|
385
|
+
{ type: actions.receivedPermissionSets.type, payload: receivedPermissionSets },
|
|
386
|
+
{ type: getPermissionSets.fulfilled.type },
|
|
387
|
+
{ type: actions.receivedRoles.type, payload: receivedRoles },
|
|
388
|
+
{ type: getRoles.fulfilled.type }
|
|
389
|
+
];
|
|
390
|
+
const store = mockStore({ ...defaultState });
|
|
391
|
+
await store.dispatch(getRoles());
|
|
392
|
+
await act(async () => {
|
|
393
|
+
jest.runOnlyPendingTimers();
|
|
394
|
+
jest.runAllTicks();
|
|
395
|
+
});
|
|
396
|
+
const storeActions = store.getActions();
|
|
397
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
398
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
399
|
+
});
|
|
400
|
+
it('should allow role creation', async () => {
|
|
401
|
+
jest.clearAllMocks();
|
|
402
|
+
const expectedActions = [
|
|
403
|
+
{ type: createRole.pending.type },
|
|
404
|
+
{ type: actions.createdRole.type, payload: defaultRole },
|
|
405
|
+
{ type: getRoles.pending.type },
|
|
406
|
+
{ type: getPermissionSets.pending.type },
|
|
407
|
+
{ type: appActions.setSnackbar.type, payload: 'The role was created successfully.' },
|
|
408
|
+
{ type: actions.receivedPermissionSets.type, payload: receivedPermissionSets },
|
|
409
|
+
{ type: getPermissionSets.fulfilled.type },
|
|
410
|
+
{ type: actions.receivedRoles.type, payload: receivedRoles },
|
|
411
|
+
{ type: getRoles.fulfilled.type },
|
|
412
|
+
{ type: createRole.fulfilled.type }
|
|
413
|
+
];
|
|
414
|
+
const store = mockStore({ ...defaultState });
|
|
415
|
+
await store.dispatch(createRole({ ...defaultRole, uiPermissions: { groups: [{ item: 'testGroup', uiPermissions: [uiPermissionsById.manage.value] }] } }));
|
|
416
|
+
const storeActions = store.getActions();
|
|
417
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
418
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
419
|
+
});
|
|
420
|
+
it('should allow role edits', async () => {
|
|
421
|
+
jest.clearAllMocks();
|
|
422
|
+
const expectedActions = [
|
|
423
|
+
{ type: editRole.pending.type },
|
|
424
|
+
{
|
|
425
|
+
type: actions.createdRole.type,
|
|
426
|
+
payload: {
|
|
427
|
+
...defaultRole,
|
|
428
|
+
name: defaultRole.name,
|
|
429
|
+
uiPermissions: {
|
|
430
|
+
...defaultRole.uiPermissions,
|
|
431
|
+
groups: { ...defaultRole.uiPermissions.groups, testGroup: [uiPermissionsById.manage.value] }
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
{ type: getRoles.pending.type },
|
|
436
|
+
{ type: getPermissionSets.pending.type },
|
|
437
|
+
{ type: appActions.setSnackbar.type, payload: 'The role has been updated.' },
|
|
438
|
+
{ type: actions.receivedPermissionSets.type, payload: receivedPermissionSets },
|
|
439
|
+
{ type: getPermissionSets.fulfilled.type },
|
|
440
|
+
{ type: actions.receivedRoles.type, payload: receivedRoles },
|
|
441
|
+
{ type: getRoles.fulfilled.type },
|
|
442
|
+
{ type: editRole.fulfilled.type }
|
|
443
|
+
];
|
|
444
|
+
const store = mockStore({ ...defaultState });
|
|
445
|
+
await store.dispatch(
|
|
446
|
+
editRole({ name: defaultRole.name, uiPermissions: { groups: [{ item: 'testGroup', uiPermissions: [uiPermissionsById.manage.value] }] } })
|
|
447
|
+
);
|
|
448
|
+
const storeActions = store.getActions();
|
|
449
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
450
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
451
|
+
});
|
|
452
|
+
it('should allow role removal', async () => {
|
|
453
|
+
jest.clearAllMocks();
|
|
454
|
+
const expectedActions = [
|
|
455
|
+
{ type: removeRole.pending.type },
|
|
456
|
+
{ type: actions.removedRole.type, payload: 'test' },
|
|
457
|
+
{ type: getRoles.pending.type },
|
|
458
|
+
{ type: getPermissionSets.pending.type },
|
|
459
|
+
{ type: appActions.setSnackbar.type, payload: 'The role was deleted successfully.' },
|
|
460
|
+
{ type: actions.receivedPermissionSets.type, payload: receivedPermissionSets },
|
|
461
|
+
{ type: getPermissionSets.fulfilled.type },
|
|
462
|
+
{ type: actions.receivedRoles.type, payload: receivedRoles },
|
|
463
|
+
{ type: getRoles.fulfilled.type },
|
|
464
|
+
{ type: removeRole.fulfilled.type }
|
|
465
|
+
];
|
|
466
|
+
const store = mockStore({ ...defaultState });
|
|
467
|
+
await store.dispatch(removeRole('test'));
|
|
468
|
+
const storeActions = store.getActions();
|
|
469
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
470
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
471
|
+
});
|
|
472
|
+
it('should allow password reset - pt. 1', async () => {
|
|
473
|
+
const store = mockStore({ ...defaultState });
|
|
474
|
+
await store.dispatch(passwordResetStart(defaultState.users.byId.a1.email)).then(() => expect(true).toEqual(true));
|
|
475
|
+
});
|
|
476
|
+
it('should allow password reset - pt. 2', async () => {
|
|
477
|
+
const store = mockStore({ ...defaultState });
|
|
478
|
+
await store.dispatch(passwordResetComplete({ secretHash: 'secretHash', newPassword: 'newPassword' })).then(() => expect(true).toEqual(true));
|
|
479
|
+
});
|
|
480
|
+
it('should allow storing global settings without deletion', async () => {
|
|
481
|
+
jest.clearAllMocks();
|
|
482
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
483
|
+
const { id_attribute, ...retrievedSettings } = defaultState.users.globalSettings;
|
|
484
|
+
const expectedActions = [
|
|
485
|
+
{ type: saveGlobalSettings.pending.type },
|
|
486
|
+
{ type: getGlobalSettings.pending.type },
|
|
487
|
+
{ type: actions.setGlobalSettings.type, payload: { ...retrievedSettings } },
|
|
488
|
+
...offlineThreshold,
|
|
489
|
+
{ type: getGlobalSettings.fulfilled.type },
|
|
490
|
+
{ type: actions.setGlobalSettings.type, payload: { ...defaultState.users.globalSettings, ...settings } },
|
|
491
|
+
{ type: saveGlobalSettings.fulfilled.type }
|
|
492
|
+
];
|
|
493
|
+
const store = mockStore({ ...defaultState });
|
|
494
|
+
await store.dispatch(saveGlobalSettings(settings));
|
|
495
|
+
const storeActions = store.getActions();
|
|
496
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
497
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
498
|
+
});
|
|
499
|
+
it('should allow storing global settings without deletion and with notification', async () => {
|
|
500
|
+
jest.clearAllMocks();
|
|
501
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
502
|
+
const { id_attribute, ...retrievedSettings } = defaultState.users.globalSettings;
|
|
503
|
+
const expectedActions = [
|
|
504
|
+
{ type: saveGlobalSettings.pending.type },
|
|
505
|
+
{ type: getGlobalSettings.pending.type },
|
|
506
|
+
{ type: actions.setGlobalSettings.type, payload: { ...retrievedSettings } },
|
|
507
|
+
...offlineThreshold,
|
|
508
|
+
{ type: getGlobalSettings.fulfilled.type },
|
|
509
|
+
{ type: actions.setGlobalSettings.type, payload: { ...defaultState.users.globalSettings, ...settings } },
|
|
510
|
+
{ type: appActions.setSnackbar.type, payload: 'Settings saved successfully' },
|
|
511
|
+
{ type: saveGlobalSettings.fulfilled.type }
|
|
512
|
+
];
|
|
513
|
+
const store = mockStore({ ...defaultState });
|
|
514
|
+
await store.dispatch(saveGlobalSettings({ ...settings, notify: true }));
|
|
515
|
+
const storeActions = store.getActions();
|
|
516
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
517
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
518
|
+
});
|
|
519
|
+
it('should allow storing user scoped settings', async () => {
|
|
520
|
+
jest.clearAllMocks();
|
|
521
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
522
|
+
const { ...settings } = defaultState.users.userSettings;
|
|
523
|
+
const expectedActions = [
|
|
524
|
+
{ type: saveUserSettings.pending.type },
|
|
525
|
+
{ type: getUserSettings.pending.type },
|
|
526
|
+
{ type: actions.setUserSettings.type, payload: settings },
|
|
527
|
+
{ type: getUserSettings.fulfilled.type },
|
|
528
|
+
{ type: actions.setUserSettings.type, payload: { ...settings, extra: 'this' } },
|
|
529
|
+
{ type: saveUserSettings.fulfilled.type }
|
|
530
|
+
];
|
|
531
|
+
const store = mockStore({ ...defaultState });
|
|
532
|
+
await store.dispatch(saveUserSettings({ extra: 'this' }));
|
|
533
|
+
const storeActions = store.getActions();
|
|
534
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
535
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
536
|
+
});
|
|
537
|
+
it('should store the visibility of the announcement shown in the header in a cookie on dismissal', async () => {
|
|
538
|
+
jest.clearAllMocks();
|
|
539
|
+
const cookies = new Cookies();
|
|
540
|
+
const expectedActions = [
|
|
541
|
+
{ type: setHideAnnouncement.pending.type },
|
|
542
|
+
{ type: appActions.setAnnouncement.type, payload: undefined },
|
|
543
|
+
{ type: setHideAnnouncement.fulfilled.type }
|
|
544
|
+
];
|
|
545
|
+
const store = mockStore({ ...defaultState, app: { ...defaultState.app, hostedAnnouncement: 'something' } });
|
|
546
|
+
await store.dispatch(setHideAnnouncement({ shouldHide: true }));
|
|
547
|
+
const storeActions = store.getActions();
|
|
548
|
+
expect(cookies.get).toHaveBeenCalledTimes(1);
|
|
549
|
+
expect(cookies.set).toHaveBeenCalledTimes(1);
|
|
550
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
551
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
552
|
+
});
|
|
553
|
+
it('should store the sizes of columns in local storage', async () => {
|
|
554
|
+
jest.clearAllMocks();
|
|
555
|
+
const expectedActions = [
|
|
556
|
+
{ type: updateUserColumnSettings.pending.type },
|
|
557
|
+
{ type: actions.setCustomColumns.type, payload: [{ asd: 'asd' }] },
|
|
558
|
+
{ type: updateUserColumnSettings.fulfilled.type }
|
|
559
|
+
];
|
|
560
|
+
const store = mockStore({ ...defaultState, users: { ...defaultState.users, customColumns: [{ asd: 'asd' }] } });
|
|
561
|
+
await store.dispatch(updateUserColumnSettings({ columns: [{ asd: 'asd' }] }));
|
|
562
|
+
const storeActions = store.getActions();
|
|
563
|
+
expect(localStorage.getItem).not.toHaveBeenCalled();
|
|
564
|
+
expect(localStorage.setItem).toHaveBeenCalled();
|
|
565
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
566
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
567
|
+
|
|
568
|
+
jest.clearAllMocks();
|
|
569
|
+
await store.dispatch(updateUserColumnSettings({}));
|
|
570
|
+
expect(localStorage.getItem).toHaveBeenCalledTimes(1);
|
|
571
|
+
expect(localStorage.setItem).toHaveBeenCalledTimes(1);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should allow token list retrieval', async () => {
|
|
575
|
+
jest.clearAllMocks();
|
|
576
|
+
const expectedActions = [
|
|
577
|
+
{ type: getTokens.pending.type },
|
|
578
|
+
{ type: actions.updatedUser.type, payload: { id: 'a1', tokens: accessTokens } },
|
|
579
|
+
{ type: getTokens.fulfilled.type }
|
|
580
|
+
];
|
|
581
|
+
const store = mockStore({ ...defaultState });
|
|
582
|
+
await store.dispatch(getTokens());
|
|
583
|
+
const storeActions = store.getActions();
|
|
584
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
585
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
586
|
+
});
|
|
587
|
+
it('should allow token generation', async () => {
|
|
588
|
+
jest.clearAllMocks();
|
|
589
|
+
const expectedActions = [
|
|
590
|
+
{ type: generateToken.pending.type },
|
|
591
|
+
{ type: getTokens.pending.type },
|
|
592
|
+
{ type: actions.updatedUser.type, payload: { id: 'a1', tokens: accessTokens } },
|
|
593
|
+
{ type: getTokens.fulfilled.type },
|
|
594
|
+
{ type: generateToken.fulfilled.type }
|
|
595
|
+
];
|
|
596
|
+
const store = mockStore({ ...defaultState });
|
|
597
|
+
const result = await store.dispatch(generateToken({ name: 'name' })).unwrap();
|
|
598
|
+
const storeActions = store.getActions();
|
|
599
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
600
|
+
expect(result[result.length - 1]).toEqual('aNewToken');
|
|
601
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
602
|
+
});
|
|
603
|
+
it('should allow token removal', async () => {
|
|
604
|
+
jest.clearAllMocks();
|
|
605
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
606
|
+
const expectedActions = [
|
|
607
|
+
{ type: revokeToken.pending.type },
|
|
608
|
+
{ type: getTokens.pending.type },
|
|
609
|
+
{ type: actions.updatedUser.type, payload: { id: 'a1', tokens: accessTokens } },
|
|
610
|
+
{ type: getTokens.fulfilled.type },
|
|
611
|
+
{ type: revokeToken.fulfilled.type }
|
|
612
|
+
];
|
|
613
|
+
const store = mockStore({ ...defaultState });
|
|
614
|
+
await store.dispatch(revokeToken({ id: 'some-id-1' }));
|
|
615
|
+
const storeActions = store.getActions();
|
|
616
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
617
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('should handle setting single tooltip read state', async () => {
|
|
621
|
+
const store = mockStore({ ...defaultState });
|
|
622
|
+
const expectedActions = [
|
|
623
|
+
{ type: setTooltipReadState.pending.type },
|
|
624
|
+
{ type: actions.setTooltipState.type, payload: { id: 'foo', readState: 'testRead' } },
|
|
625
|
+
{ type: saveUserSettings.pending.type },
|
|
626
|
+
{ type: getUserSettings.pending.type },
|
|
627
|
+
{ type: actions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
|
|
628
|
+
{ type: getUserSettings.fulfilled.type },
|
|
629
|
+
{ type: actions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
|
|
630
|
+
{ type: saveUserSettings.fulfilled.type },
|
|
631
|
+
{ type: setTooltipReadState.fulfilled.type }
|
|
632
|
+
];
|
|
633
|
+
await store.dispatch(setTooltipReadState({ id: 'foo', readState: 'testRead', persist: true }));
|
|
634
|
+
const storeActions = store.getActions();
|
|
635
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
636
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
637
|
+
});
|
|
638
|
+
it('should handle setting tooltip read state for all tips', async () => {
|
|
639
|
+
const store = mockStore({ ...defaultState });
|
|
640
|
+
|
|
641
|
+
const expectedActions = [
|
|
642
|
+
{ type: setAllTooltipsReadState.pending.type },
|
|
643
|
+
{
|
|
644
|
+
type: actions.setTooltipsState.type,
|
|
645
|
+
payload: { ...Object.values(HELPTOOLTIPS).reduce((accu, { id }) => ({ ...accu, [id]: { readState: 'testRead' } }), {}) }
|
|
646
|
+
},
|
|
647
|
+
{ type: saveUserSettings.pending.type },
|
|
648
|
+
{ type: getUserSettings.pending.type },
|
|
649
|
+
{ type: actions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
|
|
650
|
+
{ type: getUserSettings.fulfilled.type },
|
|
651
|
+
{ type: actions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
|
|
652
|
+
{ type: saveUserSettings.fulfilled.type },
|
|
653
|
+
{ type: setAllTooltipsReadState.fulfilled.type }
|
|
654
|
+
];
|
|
655
|
+
await store.dispatch(setAllTooltipsReadState('testRead'));
|
|
656
|
+
const storeActions = store.getActions();
|
|
657
|
+
expect(storeActions.length).toEqual(expectedActions.length);
|
|
658
|
+
expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
|
|
659
|
+
});
|
|
660
|
+
});
|