@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.
Files changed (284) hide show
  1. package/.eslintrc.cjs +9 -0
  2. package/.prettierignore +1 -0
  3. package/CHANGELOG.md +12 -0
  4. package/actions.ts +32 -0
  5. package/api/general-api.test.ts +51 -0
  6. package/api/general-api.ts +60 -0
  7. package/api/types/AWSCredentials.ts +16 -0
  8. package/api/types/Actor.ts +35 -0
  9. package/api/types/Addon.ts +17 -0
  10. package/api/types/AddonTenantadm.ts +17 -0
  11. package/api/types/Alert.ts +34 -0
  12. package/api/types/AlertDetails.ts +23 -0
  13. package/api/types/AlertSubject.ts +23 -0
  14. package/api/types/ApiBurst.ts +21 -0
  15. package/api/types/ApiBurstTenantadm.ts +21 -0
  16. package/api/types/ApiLimits.ts +16 -0
  17. package/api/types/ApiLimitsTenantadm.ts +16 -0
  18. package/api/types/ApiQuota.ts +17 -0
  19. package/api/types/ApiQuotaTenantadm.ts +17 -0
  20. package/api/types/Artifact.ts +48 -0
  21. package/api/types/ArtifactDeployments.ts +48 -0
  22. package/api/types/ArtifactInfo.ts +11 -0
  23. package/api/types/ArtifactInfoDeployments.ts +11 -0
  24. package/api/types/ArtifactLink.ts +11 -0
  25. package/api/types/ArtifactTypeInfo.ts +13 -0
  26. package/api/types/ArtifactTypeInfoDeployments.ts +13 -0
  27. package/api/types/ArtifactUpdate.ts +10 -0
  28. package/api/types/ArtifactUploadLink.ts +15 -0
  29. package/api/types/Attribute.ts +36 -0
  30. package/api/types/AttributeInventory.ts +32 -0
  31. package/api/types/AuditLog.ts +26 -0
  32. package/api/types/AuthSet.ts +33 -0
  33. package/api/types/AuthSetDeviceauth.ts +37 -0
  34. package/api/types/AutoAuthRequest.ts +23 -0
  35. package/api/types/AzureSharedAccessSecret.ts +11 -0
  36. package/api/types/BillingInfo.ts +13 -0
  37. package/api/types/BinaryDeltaConfiguration.ts +15 -0
  38. package/api/types/BinaryDeltaConfigurationDeployments.ts +15 -0
  39. package/api/types/BinaryDeltaLimits.ts +15 -0
  40. package/api/types/BinaryDeltaLimitsDeployments.ts +15 -0
  41. package/api/types/BoundingBox.ts +11 -0
  42. package/api/types/CancelRequest.ts +10 -0
  43. package/api/types/CardData.ts +49 -0
  44. package/api/types/CardSetupData.ts +17 -0
  45. package/api/types/CheckoutData.ts +13 -0
  46. package/api/types/Configuration.ts +11 -0
  47. package/api/types/Count.ts +13 -0
  48. package/api/types/Credentials.ts +23 -0
  49. package/api/types/DBusSubsystem.ts +27 -0
  50. package/api/types/DeltaConfiguration.ts +14 -0
  51. package/api/types/DeltaConfigurationDeployments.ts +14 -0
  52. package/api/types/Deployment.ts +95 -0
  53. package/api/types/DeploymentAggregation.ts +18 -0
  54. package/api/types/DeploymentAggregationItem.ts +18 -0
  55. package/api/types/DeploymentAggregationTerm.ts +23 -0
  56. package/api/types/DeploymentAggregationTerms.ts +18 -0
  57. package/api/types/DeploymentAttributeProjection.ts +11 -0
  58. package/api/types/DeploymentAuditlogs.ts +11 -0
  59. package/api/types/DeploymentDeployments.ts +99 -0
  60. package/api/types/DeploymentFilterTerm.ts +37 -0
  61. package/api/types/DeploymentIdentifier.ts +10 -0
  62. package/api/types/DeploymentPhase.ts +23 -0
  63. package/api/types/DeploymentReporting.ts +46 -0
  64. package/api/types/DeploymentSearchTerms.ts +39 -0
  65. package/api/types/DeploymentSortTerm.ts +25 -0
  66. package/api/types/DeploymentStatistics.ts +15 -0
  67. package/api/types/DeploymentStatusStatistics.ts +54 -0
  68. package/api/types/Device.ts +29 -0
  69. package/api/types/DeviceAggregation.ts +18 -0
  70. package/api/types/DeviceAggregationItem.ts +18 -0
  71. package/api/types/DeviceAggregationTerm.ts +27 -0
  72. package/api/types/DeviceAggregationTerms.ts +22 -0
  73. package/api/types/DeviceAttribute.ts +23 -0
  74. package/api/types/DeviceAttributeProjection.ts +15 -0
  75. package/api/types/DeviceAuditlogs.ts +17 -0
  76. package/api/types/DeviceAuthEvent.ts +23 -0
  77. package/api/types/DeviceConfiguration.ts +16 -0
  78. package/api/types/DeviceDeployment.ts +11 -0
  79. package/api/types/DeviceDeployments.ts +31 -0
  80. package/api/types/DeviceDeviceauth.ts +42 -0
  81. package/api/types/DeviceFilterAttribute.ts +22 -0
  82. package/api/types/DeviceFilterTerm.ts +41 -0
  83. package/api/types/DeviceInventory.ts +19 -0
  84. package/api/types/DeviceInventoryInventory.ts +19 -0
  85. package/api/types/DeviceReporting.ts +22 -0
  86. package/api/types/DeviceSearchTerms.ts +39 -0
  87. package/api/types/DeviceSortTerm.ts +29 -0
  88. package/api/types/DeviceState.ts +32 -0
  89. package/api/types/DeviceStateDeviceconnect.ts +33 -0
  90. package/api/types/DeviceStateIot_manager.ts +14 -0
  91. package/api/types/DeviceStatus.ts +19 -0
  92. package/api/types/DeviceWithImage.ts +80 -0
  93. package/api/types/DirectUploadMetadata.ts +18 -0
  94. package/api/types/EmailVerificationCompletion.ts +13 -0
  95. package/api/types/EmailVerificationRequest.ts +13 -0
  96. package/api/types/Error.ts +17 -0
  97. package/api/types/ErrorAuditlogs.ts +13 -0
  98. package/api/types/ErrorDeployments.ts +17 -0
  99. package/api/types/ErrorDeviceauth.ts +13 -0
  100. package/api/types/ErrorDeviceconfig.ts +17 -0
  101. package/api/types/ErrorDeviceconnect.ts +17 -0
  102. package/api/types/ErrorDevicemonitor.ts +17 -0
  103. package/api/types/ErrorExt.ts +18 -0
  104. package/api/types/ErrorInventory.ts +17 -0
  105. package/api/types/ErrorIot_manager.ts +17 -0
  106. package/api/types/ErrorNotFound.ts +17 -0
  107. package/api/types/ErrorReporting.ts +18 -0
  108. package/api/types/ErrorTenantadm.ts +13 -0
  109. package/api/types/ErrorUseradm.ts +17 -0
  110. package/api/types/Event.ts +48 -0
  111. package/api/types/ExternalIdentity.ts +17 -0
  112. package/api/types/Features.ts +17 -0
  113. package/api/types/FileUpload.ts +23 -0
  114. package/api/types/Filter.ts +19 -0
  115. package/api/types/FilterAttribute.ts +21 -0
  116. package/api/types/FilterDefinition.ts +18 -0
  117. package/api/types/FilterDeployments.ts +19 -0
  118. package/api/types/FilterInventory.ts +20 -0
  119. package/api/types/FilterPredicate.ts +78 -0
  120. package/api/types/FilterPredicateDeployments.ts +48 -0
  121. package/api/types/FilterPredicateInventory.ts +76 -0
  122. package/api/types/GeoBoundingBox.ts +10 -0
  123. package/api/types/GeoBoundingBoxFilter.ts +6 -0
  124. package/api/types/GeoDistance.ts +17 -0
  125. package/api/types/GeoDistanceFilter.ts +10 -0
  126. package/api/types/GeoPoint.ts +15 -0
  127. package/api/types/Group.ts +10 -0
  128. package/api/types/HTTP.ts +19 -0
  129. package/api/types/IdentityData.ts +25 -0
  130. package/api/types/InputParameter.ts +8 -0
  131. package/api/types/Integration.ts +24 -0
  132. package/api/types/JobStatus.ts +13 -0
  133. package/api/types/Limit.ts +21 -0
  134. package/api/types/LimitDeployments.ts +21 -0
  135. package/api/types/LimitDeviceauth.ts +10 -0
  136. package/api/types/LimitUseradm.ts +11 -0
  137. package/api/types/Limits.ts +11 -0
  138. package/api/types/LineDescriptor.ts +11 -0
  139. package/api/types/LogSubsystem.ts +23 -0
  140. package/api/types/LoginOptions.ts +18 -0
  141. package/api/types/ManagementAPIConfiguration.ts +5 -0
  142. package/api/types/MenderTypes.ts +183 -0
  143. package/api/types/MonitorConfiguration.ts +22 -0
  144. package/api/types/NewConfigurationDeployment.ts +15 -0
  145. package/api/types/NewConfigurationDeploymentResponse.ts +10 -0
  146. package/api/types/NewDeployment.ts +42 -0
  147. package/api/types/NewDeploymentForGroup.ts +33 -0
  148. package/api/types/NewDeploymentPhase.ts +37 -0
  149. package/api/types/NewDeploymentPhaseDeployments.ts +20 -0
  150. package/api/types/NewDeploymentV2.ts +31 -0
  151. package/api/types/NewTenant.ts +41 -0
  152. package/api/types/Object.ts +35 -0
  153. package/api/types/OwnUserUpdate.ts +21 -0
  154. package/api/types/PasswordResetCompletion.ts +17 -0
  155. package/api/types/PasswordResetRequest.ts +13 -0
  156. package/api/types/Permission.ts +21 -0
  157. package/api/types/PermissionObject.ts +14 -0
  158. package/api/types/PermissionSet.ts +22 -0
  159. package/api/types/PermissionSetWithScope.ts +15 -0
  160. package/api/types/PersonalAccessToken.ts +29 -0
  161. package/api/types/PersonalAccessTokenRequest.ts +18 -0
  162. package/api/types/Plan.ts +19 -0
  163. package/api/types/PlanBindingDetails.ts +13 -0
  164. package/api/types/PlanChangeRequest.ts +14 -0
  165. package/api/types/PlanLimits.ts +12 -0
  166. package/api/types/PreAuthSet.ts +16 -0
  167. package/api/types/Release.ts +32 -0
  168. package/api/types/ReleaseDeployments.ts +32 -0
  169. package/api/types/ReleaseUpdate.ts +13 -0
  170. package/api/types/Releases.ts +9 -0
  171. package/api/types/ReleasesDeleteError.ts +21 -0
  172. package/api/types/ReleasesDeployments.ts +9 -0
  173. package/api/types/Role.ts +19 -0
  174. package/api/types/RolePermission.ts +25 -0
  175. package/api/types/RolePermissionObject.ts +17 -0
  176. package/api/types/RoleUseradm.ts +19 -0
  177. package/api/types/SAMLMetadata.ts +21 -0
  178. package/api/types/Scope.ts +19 -0
  179. package/api/types/SelectAttribute.ts +17 -0
  180. package/api/types/ServiceSubsystem.ts +19 -0
  181. package/api/types/Settings.ts +23 -0
  182. package/api/types/SortCriteria.ts +30 -0
  183. package/api/types/SsoObject.ts +21 -0
  184. package/api/types/Status.ts +17 -0
  185. package/api/types/StatusDeviceauth.ts +18 -0
  186. package/api/types/StatusTenantadm.ts +19 -0
  187. package/api/types/StorageLimit.ts +17 -0
  188. package/api/types/SubscriptionData.ts +8 -0
  189. package/api/types/SupportRequest.ts +17 -0
  190. package/api/types/Tag.ts +27 -0
  191. package/api/types/Tags.ts +10 -0
  192. package/api/types/TaskResult.ts +13 -0
  193. package/api/types/TaskResultCLI.ts +10 -0
  194. package/api/types/TaskResultHTTPRequest.ts +8 -0
  195. package/api/types/TaskResultHTTPResponse.ts +10 -0
  196. package/api/types/Tenant.ts +38 -0
  197. package/api/types/TenantApiLimits.ts +12 -0
  198. package/api/types/TenantApiLimitsTenantadm.ts +12 -0
  199. package/api/types/TenantIdName.ts +14 -0
  200. package/api/types/TenantIdsData.ts +10 -0
  201. package/api/types/TenantInfo.ts +14 -0
  202. package/api/types/TenantShortInfo.ts +21 -0
  203. package/api/types/TenantTenantadm.ts +38 -0
  204. package/api/types/TenantsIdName.ts +6 -0
  205. package/api/types/Update.ts +17 -0
  206. package/api/types/UpdateDeployments.ts +18 -0
  207. package/api/types/UpdateFile.ts +13 -0
  208. package/api/types/UpdateFileDeployments.ts +13 -0
  209. package/api/types/UpdateTypes.ts +8 -0
  210. package/api/types/UpgradeCompleteRequest.ts +23 -0
  211. package/api/types/User.ts +13 -0
  212. package/api/types/UserAuditlogs.ts +13 -0
  213. package/api/types/UserNew.ts +26 -0
  214. package/api/types/UserUpdate.ts +25 -0
  215. package/api/types/UserUseradm.ts +45 -0
  216. package/api/types/UserWithTenantInfo.ts +8 -0
  217. package/api/types/XDeltaArgs.ts +9 -0
  218. package/api/types/XDeltaArgsDeployments.ts +9 -0
  219. package/api/types/XDeltaArgsLimits.ts +10 -0
  220. package/api/types/XDeltaArgsLimitsDeployments.ts +10 -0
  221. package/api/users-api.ts +34 -0
  222. package/appSlice/constants.ts +166 -0
  223. package/appSlice/index.ts +159 -0
  224. package/appSlice/reducer.test.ts +97 -0
  225. package/appSlice/selectors.ts +33 -0
  226. package/appSlice/thunks.test.ts +118 -0
  227. package/appSlice/thunks.ts +148 -0
  228. package/auth.test.ts +36 -0
  229. package/auth.ts +82 -0
  230. package/commonConstants.tsx +252 -0
  231. package/commonSelectors.ts +255 -0
  232. package/constants.ts +22 -0
  233. package/deploymentsSlice/constants.ts +175 -0
  234. package/deploymentsSlice/index.ts +145 -0
  235. package/deploymentsSlice/reducer.test.ts +108 -0
  236. package/deploymentsSlice/selectors.ts +58 -0
  237. package/deploymentsSlice/thunks.test.ts +425 -0
  238. package/deploymentsSlice/thunks.ts +382 -0
  239. package/devicesSlice/constants.ts +56 -0
  240. package/devicesSlice/index.ts +207 -0
  241. package/devicesSlice/reducer.test.ts +194 -0
  242. package/devicesSlice/selectors.ts +127 -0
  243. package/devicesSlice/thunks.test.tsx +1245 -0
  244. package/devicesSlice/thunks.tsx +1098 -0
  245. package/monitorSlice/constants.ts +17 -0
  246. package/monitorSlice/index.ts +65 -0
  247. package/monitorSlice/reducer.test.ts +79 -0
  248. package/monitorSlice/selectors.ts +15 -0
  249. package/monitorSlice/thunks.test.ts +112 -0
  250. package/monitorSlice/thunks.ts +101 -0
  251. package/onboardingSlice/constants.ts +53 -0
  252. package/onboardingSlice/index.ts +60 -0
  253. package/onboardingSlice/reducer.test.ts +45 -0
  254. package/onboardingSlice/selectors.ts +15 -0
  255. package/onboardingSlice/thunks.test.ts +235 -0
  256. package/onboardingSlice/thunks.ts +142 -0
  257. package/organizationSlice/constants.ts +82 -0
  258. package/organizationSlice/index.ts +111 -0
  259. package/organizationSlice/reducer.test.ts +78 -0
  260. package/organizationSlice/selectors.ts +45 -0
  261. package/organizationSlice/thunks.test.ts +619 -0
  262. package/organizationSlice/thunks.ts +383 -0
  263. package/package.json +38 -0
  264. package/releasesSlice/constants.ts +18 -0
  265. package/releasesSlice/index.ts +116 -0
  266. package/releasesSlice/reducer.test.ts +78 -0
  267. package/releasesSlice/selectors.ts +33 -0
  268. package/releasesSlice/thunks.test.ts +389 -0
  269. package/releasesSlice/thunks.ts +379 -0
  270. package/selectors.ts +22 -0
  271. package/store.ts +89 -0
  272. package/storehooks.test.tsx +386 -0
  273. package/storehooks.ts +250 -0
  274. package/thunks.ts +21 -0
  275. package/tsconfig.json +11 -0
  276. package/tsup.config.ts +11 -0
  277. package/usersSlice/constants.ts +374 -0
  278. package/usersSlice/index.ts +151 -0
  279. package/usersSlice/reducer.test.ts +141 -0
  280. package/usersSlice/selectors.ts +60 -0
  281. package/usersSlice/thunks.test.ts +660 -0
  282. package/usersSlice/thunks.ts +773 -0
  283. package/utils.test.ts +187 -0
  284. package/utils.ts +249 -0
@@ -0,0 +1,235 @@
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 { getUserSettings, saveUserSettings } from '@northern.tech/store/thunks';
16
+ import configureMockStore from 'redux-mock-store';
17
+ import { thunk } from 'redux-thunk';
18
+
19
+ import { actions } from '.';
20
+ import { defaultState } from '../../../../tests/mockData';
21
+ import { actions as userActions } from '../usersSlice';
22
+ import { onboardingSteps } from './constants';
23
+ import { advanceOnboarding, getOnboardingState, setOnboardingApproach, setOnboardingCanceled, setOnboardingComplete, setOnboardingDeviceType } from './thunks';
24
+
25
+ const middlewares = [thunk];
26
+ const mockStore = configureMockStore(middlewares);
27
+
28
+ export const defaultOnboardingState = {
29
+ approach: null,
30
+ complete: false,
31
+ demoArtifactPort: 85,
32
+ deviceType: null,
33
+ progress: undefined,
34
+ showConnectDeviceDialog: false,
35
+ showTips: undefined,
36
+ showTipsDialog: false,
37
+ something: 'here'
38
+ };
39
+
40
+ export const expectedOnboardingActions = [
41
+ { type: getOnboardingState.pending.type },
42
+ { type: actions.setOnboardingComplete.type, payload: false },
43
+ {
44
+ type: actions.setOnboardingState.type,
45
+ payload: {
46
+ ...defaultOnboardingState,
47
+ address: 'http://192.168.10.141:85',
48
+ approach: 'physical',
49
+ deviceType: ['raspberrypi4'],
50
+ progress: 'devices-accepted-onboarding',
51
+ showTips: true
52
+ }
53
+ },
54
+ { type: saveUserSettings.pending.type },
55
+ { type: getUserSettings.pending.type },
56
+ { type: userActions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
57
+ { type: getUserSettings.fulfilled.type },
58
+ {
59
+ type: userActions.setUserSettings.type,
60
+ payload: {
61
+ ...defaultState.users.userSettings,
62
+ onboarding: {
63
+ ...defaultOnboardingState,
64
+ address: 'http://192.168.10.141:85',
65
+ approach: 'physical',
66
+ deviceType: ['raspberrypi4'],
67
+ progress: 'devices-accepted-onboarding',
68
+ showTips: true
69
+ }
70
+ }
71
+ },
72
+ { type: saveUserSettings.fulfilled.type },
73
+ { type: getOnboardingState.fulfilled.type }
74
+ ];
75
+
76
+ describe('onboarding actions', () => {
77
+ it('should pass on onboarding completion', async () => {
78
+ const store = mockStore({ ...defaultState });
79
+ await store.dispatch(setOnboardingComplete(true));
80
+ const expectedActions = [
81
+ { type: setOnboardingComplete.pending.type },
82
+ { type: actions.setOnboardingComplete.type, payload: true },
83
+ { type: actions.setShowOnboardingHelp.type, payload: false },
84
+ { type: advanceOnboarding.pending.type },
85
+ { type: actions.setOnboardingProgress.type, payload: onboardingSteps.DEPLOYMENTS_PAST_COMPLETED_FAILURE },
86
+ { type: saveUserSettings.pending.type },
87
+ { type: getUserSettings.pending.type },
88
+ { type: userActions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
89
+ { type: getUserSettings.fulfilled.type },
90
+ {
91
+ type: userActions.setUserSettings.type,
92
+ payload: {
93
+ ...defaultState.users.userSettings,
94
+ onboarding: {
95
+ ...defaultOnboardingState,
96
+ complete: true,
97
+ progress: onboardingSteps.DEPLOYMENTS_PAST_COMPLETED_FAILURE
98
+ }
99
+ }
100
+ },
101
+ { type: saveUserSettings.fulfilled.type },
102
+ { type: advanceOnboarding.fulfilled.type },
103
+ { type: setOnboardingComplete.fulfilled.type }
104
+ ];
105
+ const storeActions = store.getActions();
106
+ expect(storeActions.length).toEqual(expectedActions.length);
107
+ expectedActions.map((action, index) => Object.keys(action).map(key => expect(storeActions[index][key]).toEqual(action[key])));
108
+ });
109
+ it('should pass on onboarding approach', async () => {
110
+ const store = mockStore({ ...defaultState });
111
+ await store.dispatch(setOnboardingApproach('test'));
112
+ const expectedActions = [
113
+ { type: setOnboardingApproach.pending.type },
114
+ { type: actions.setOnboardingApproach.type, payload: 'test' },
115
+ { type: saveUserSettings.pending.type },
116
+ { type: getUserSettings.pending.type },
117
+ { type: userActions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
118
+ { type: getUserSettings.fulfilled.type },
119
+ {
120
+ type: userActions.setUserSettings.type,
121
+ payload: {
122
+ ...defaultState.users.userSettings,
123
+ onboarding: {
124
+ ...defaultOnboardingState,
125
+ approach: 'test'
126
+ }
127
+ }
128
+ },
129
+ { type: saveUserSettings.fulfilled.type },
130
+ { type: setOnboardingApproach.fulfilled.type }
131
+ ];
132
+ const storeActions = store.getActions();
133
+ expect(storeActions.length).toEqual(expectedActions.length);
134
+ expectedActions.map((action, index) => Object.keys(action).map(key => expect(storeActions[index][key]).toEqual(action[key])));
135
+ });
136
+ it('should pass on onboarding device type', async () => {
137
+ const store = mockStore({ ...defaultState });
138
+ await store.dispatch(setOnboardingDeviceType('testtype'));
139
+ const expectedActions = [
140
+ { type: setOnboardingDeviceType.pending.type },
141
+ { type: actions.setOnboardingDeviceType.type, payload: 'testtype' },
142
+ { type: saveUserSettings.pending.type },
143
+ { type: getUserSettings.pending.type },
144
+ { type: userActions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
145
+ { type: getUserSettings.fulfilled.type },
146
+ {
147
+ type: userActions.setUserSettings.type,
148
+ payload: {
149
+ ...defaultState.users.userSettings,
150
+ columnSelection: [],
151
+ onboarding: {
152
+ ...defaultOnboardingState,
153
+ deviceType: 'testtype'
154
+ }
155
+ }
156
+ },
157
+ { type: saveUserSettings.fulfilled.type },
158
+ { type: setOnboardingDeviceType.fulfilled.type }
159
+ ];
160
+ const storeActions = store.getActions();
161
+ expect(storeActions.length).toEqual(expectedActions.length);
162
+ expectedActions.map((action, index) => Object.keys(action).map(key => expect(storeActions[index][key]).toEqual(action[key])));
163
+ });
164
+ it('should advance onboarding by one step', async () => {
165
+ const store = mockStore({ ...defaultState });
166
+ await store.dispatch(advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START));
167
+ const expectedActions = [
168
+ { type: advanceOnboarding.pending.type },
169
+ { type: actions.setOnboardingProgress.type, payload: onboardingSteps.DEVICES_PENDING_ONBOARDING_START },
170
+ { type: saveUserSettings.pending.type },
171
+ { type: getUserSettings.pending.type },
172
+ { type: userActions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
173
+ { type: getUserSettings.fulfilled.type },
174
+ {
175
+ type: userActions.setUserSettings.type,
176
+ payload: {
177
+ ...defaultState.users.userSettings,
178
+ columnSelection: [],
179
+ onboarding: {
180
+ ...defaultOnboardingState,
181
+ progress: onboardingSteps.DEVICES_PENDING_ONBOARDING_START
182
+ }
183
+ }
184
+ },
185
+ { type: saveUserSettings.fulfilled.type },
186
+ { type: advanceOnboarding.fulfilled.type }
187
+ ];
188
+ const storeActions = store.getActions();
189
+ expect(storeActions.length).toEqual(expectedActions.length);
190
+ expectedActions.map((action, index) => Object.keys(action).map(key => expect(storeActions[index][key]).toEqual(action[key])));
191
+ });
192
+ it('should disable helptips and store a canceled state', async () => {
193
+ const store = mockStore({ ...defaultState });
194
+ await store.dispatch(setOnboardingCanceled());
195
+ const expectedActions = [
196
+ { type: setOnboardingCanceled.pending.type },
197
+ { type: actions.setShowOnboardingHelp.type, payload: false },
198
+ { type: actions.setShowDismissOnboardingTipsDialog.type, payload: false },
199
+ { type: actions.setOnboardingComplete.type, payload: true },
200
+ { type: advanceOnboarding.pending.type },
201
+ { type: actions.setOnboardingProgress.type, payload: 'onboarding-canceled' },
202
+ { type: saveUserSettings.pending.type },
203
+ { type: getUserSettings.pending.type },
204
+ { type: userActions.setUserSettings.type, payload: { ...defaultState.users.userSettings } },
205
+ { type: getUserSettings.fulfilled.type },
206
+ {
207
+ type: userActions.setUserSettings.type,
208
+ payload: {
209
+ ...defaultState.users.userSettings,
210
+ columnSelection: [],
211
+ onboarding: {
212
+ ...defaultOnboardingState,
213
+ complete: true,
214
+ progress: 'onboarding-canceled'
215
+ }
216
+ }
217
+ },
218
+ { type: saveUserSettings.fulfilled.type },
219
+ { type: advanceOnboarding.fulfilled.type },
220
+ { type: setOnboardingCanceled.fulfilled.type }
221
+ ];
222
+ const storeActions = store.getActions();
223
+ expect(storeActions.length).toEqual(expectedActions.length);
224
+ expectedActions.map((action, index) => Object.keys(action).map(key => expect(storeActions[index][key]).toEqual(action[key])));
225
+ });
226
+ it('should try to derive the onboarding state based on the stored state of the environment', async () => {
227
+ const store = mockStore({ ...defaultState });
228
+ const stepNames = Object.keys(onboardingSteps);
229
+ await store.dispatch(getOnboardingState(stepNames[0]));
230
+ const expectedActions = expectedOnboardingActions;
231
+ const storeActions = store.getActions();
232
+ expect(storeActions.length).toEqual(expectedActions.length);
233
+ expectedActions.map((action, index) => Object.keys(action).map(key => expect(storeActions[index][key]).toEqual(action[key])));
234
+ });
235
+ });
@@ -0,0 +1,142 @@
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 { DEVICE_STATES, onboardingSteps as onboardingStepNames, orderedOnboardingSteps as onboardingSteps } from '@northern.tech/store/constants';
16
+ import { getOnboardingState as getCurrentOnboardingState, getUserCapabilities } from '@northern.tech/store/selectors';
17
+ import { saveUserSettings } from '@northern.tech/store/thunks';
18
+ import { getDemoDeviceAddress } from '@northern.tech/utils/helpers';
19
+ import { createAsyncThunk } from '@reduxjs/toolkit';
20
+ import Cookies from 'universal-cookie';
21
+
22
+ import { actions, sliceName } from '.';
23
+
24
+ const cookies = new Cookies();
25
+
26
+ export const applyOnboardingFallbacks = progress => {
27
+ const step = onboardingSteps[progress];
28
+ if (step && step.fallbackStep) {
29
+ return step.fallbackStep;
30
+ }
31
+ return progress;
32
+ };
33
+
34
+ const determineProgress = (acceptedDevices, pendingDevices, releases, pastDeployments) => {
35
+ const steps = onboardingSteps;
36
+ let progress = -1;
37
+ progress = pendingDevices.length > 1 ? steps.findIndex(step => step === onboardingStepNames.DEVICES_PENDING_ACCEPTING_ONBOARDING) : progress;
38
+ progress = acceptedDevices.length >= 1 ? steps.findIndex(step => step === onboardingStepNames.DEVICES_ACCEPTED_ONBOARDING) : progress;
39
+ progress =
40
+ acceptedDevices.length > 1 && releases.length > 1 && pastDeployments.length > 1
41
+ ? steps.findIndex(step => step === onboardingStepNames.DEPLOYMENTS_PAST_COMPLETED)
42
+ : progress;
43
+ return steps[progress];
44
+ };
45
+
46
+ const deductOnboardingState = ({ devicesById, devicesByStatus, onboardingState, pastDeployments, releases, userCapabilities, userId }) => {
47
+ const { canDeploy, canManageDevices, canReadDeployments, canReadDevices, canReadReleases, canUploadReleases } = userCapabilities;
48
+ const userCookie = cookies.get(`${userId}-onboarded`);
49
+ const acceptedDevices = devicesByStatus[DEVICE_STATES.accepted].deviceIds;
50
+ const pendingDevices = devicesByStatus[DEVICE_STATES.pending].deviceIds;
51
+ let deviceType = onboardingState.deviceType ?? [];
52
+ deviceType =
53
+ !deviceType.length && acceptedDevices.length && devicesById[acceptedDevices[0]].hasOwnProperty('attributes')
54
+ ? devicesById[acceptedDevices[0]].attributes.device_type
55
+ : deviceType;
56
+ const progress = applyOnboardingFallbacks(onboardingState.progress || determineProgress(acceptedDevices, pendingDevices, releases, pastDeployments));
57
+ return {
58
+ ...onboardingState,
59
+ complete: !!(
60
+ Boolean(userCookie) ||
61
+ onboardingState.complete ||
62
+ (acceptedDevices.length > 1 && pendingDevices.length > 0 && releases.length > 1 && pastDeployments.length > 1) ||
63
+ (acceptedDevices.length >= 1 && releases.length >= 2 && pastDeployments.length > 2) ||
64
+ (acceptedDevices.length >= 1 && pendingDevices.length > 0 && releases.length >= 2 && pastDeployments.length >= 2) ||
65
+ onboardingSteps.findIndex(step => step === progress) >= onboardingSteps.length - 1 ||
66
+ onboardingState.disable ||
67
+ ![canDeploy, canManageDevices, canReadDeployments, canReadDevices, canReadReleases, canUploadReleases].every(i => i)
68
+ ),
69
+ showTips: onboardingState.showTips != null ? onboardingState.showTips : true,
70
+ deviceType,
71
+ approach: onboardingState.approach || (deviceType.some(type => type.startsWith('qemu')) ? 'virtual' : 'physical'),
72
+ progress
73
+ };
74
+ };
75
+
76
+ export const getOnboardingState = createAsyncThunk(`${sliceName}/getOnboardingState`, (_, { dispatch, getState }) => {
77
+ const state = getState();
78
+ let onboardingState = getCurrentOnboardingState(state);
79
+ if (!onboardingState.complete) {
80
+ const userId = state.users.currentUser;
81
+ onboardingState = deductOnboardingState({
82
+ devicesById: state.devices.byId,
83
+ devicesByStatus: state.devices.byStatus,
84
+ onboardingState,
85
+ pastDeployments: state.deployments.byStatus.finished.deploymentIds,
86
+ releases: Object.values(state.releases.byId),
87
+ userCapabilities: getUserCapabilities(state),
88
+ userId
89
+ });
90
+ }
91
+ onboardingState.progress = onboardingState.progress || onboardingStepNames.DASHBOARD_ONBOARDING_START;
92
+ const demoDeviceAddress = `http://${getDemoDeviceAddress(Object.values(state.devices.byId), onboardingState.approach)}`;
93
+ onboardingState.address = state.onboarding.demoArtifactPort ? `${demoDeviceAddress}:${state.onboarding.demoArtifactPort}` : demoDeviceAddress;
94
+ return Promise.all([
95
+ dispatch(actions.setOnboardingComplete(onboardingState.complete)),
96
+ dispatch(actions.setOnboardingState(onboardingState)),
97
+ dispatch(saveUserSettings({ onboarding: onboardingState }))
98
+ ]);
99
+ });
100
+
101
+ export const setOnboardingDeviceType = createAsyncThunk(`${sliceName}/setOnboardingDeviceType`, (value, { dispatch }) =>
102
+ Promise.all([dispatch(actions.setOnboardingDeviceType(value)), dispatch(saveUserSettings({ onboarding: { deviceType: value } }))])
103
+ );
104
+
105
+ export const setOnboardingApproach = createAsyncThunk(`${sliceName}/setOnboardingApproach`, (value, { dispatch }) =>
106
+ Promise.all([dispatch(actions.setOnboardingApproach(value)), dispatch(saveUserSettings({ onboarding: { approach: value } }))])
107
+ );
108
+
109
+ export const setOnboardingComplete = createAsyncThunk(`${sliceName}/setOnboardingComplete`, (value, { dispatch }) => {
110
+ let tasks = [Promise.resolve(dispatch(actions.setOnboardingComplete(value)))];
111
+ if (value) {
112
+ tasks.push(Promise.resolve(dispatch(actions.setShowOnboardingHelp(false))));
113
+ tasks.push(Promise.resolve(dispatch(advanceOnboarding(onboardingStepNames.DEPLOYMENTS_PAST_COMPLETED))));
114
+ }
115
+ return Promise.all(tasks);
116
+ });
117
+
118
+ export const setOnboardingCanceled = createAsyncThunk(`${sliceName}/setOnboardingCanceled`, (_, { dispatch }) =>
119
+ Promise.all([
120
+ Promise.resolve(dispatch(actions.setShowOnboardingHelp(false))),
121
+ Promise.resolve(dispatch(actions.setShowDismissOnboardingTipsDialog(false))),
122
+ Promise.resolve(dispatch(actions.setOnboardingComplete(true)))
123
+ ])
124
+ // using DEPLOYMENTS_PAST_COMPLETED to ensure we get the intended onboarding state set after
125
+ // _advancing_ the onboarding progress
126
+ .then(() => dispatch(advanceOnboarding(onboardingStepNames.DEPLOYMENTS_PAST_COMPLETED_FAILURE)))
127
+ );
128
+
129
+ export const advanceOnboarding = createAsyncThunk(`${sliceName}/advanceOnboarding`, (stepId, { dispatch, getState }) => {
130
+ const steps = onboardingSteps;
131
+ const progress = steps.findIndex(step => step === getState().onboarding.progress);
132
+ const stepIndex = steps.findIndex(step => step === stepId);
133
+ // if there is no progress set yet, the onboarding state deduction hasn't happened
134
+ // and the subsequent settings persistence would overwrite what we stored
135
+ if (progress > stepIndex || getState().onboarding.progress === null) {
136
+ return;
137
+ }
138
+ const madeProgress = steps[stepIndex + 1];
139
+ const state = { ...getCurrentOnboardingState(getState()), progress: madeProgress };
140
+ state.complete = stepIndex + 1 >= steps.findIndex(step => step === onboardingStepNames.DEPLOYMENTS_PAST_COMPLETED_FAILURE) ? true : state.complete;
141
+ return Promise.all([dispatch(actions.setOnboardingProgress(madeProgress)), dispatch(saveUserSettings({ onboarding: state }))]);
142
+ });
@@ -0,0 +1,82 @@
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 { Integration } from '@northern.tech/store/api/types/MenderTypes';
16
+ import { EXTERNAL_PROVIDER, apiUrl, useradmApiUrl } from '@northern.tech/store/constants';
17
+
18
+ export const auditLogsApiUrl = `${apiUrl.v1}/auditlogs`;
19
+ export const tenantadmApiUrlv1 = `${apiUrl.v1}/tenantadm`;
20
+ export const tenantadmApiUrlv2 = `${apiUrl.v2}/tenantadm`;
21
+ export const ssoIdpApiUrlv1 = `${apiUrl.v1}/useradm/sso/idp/metadata`;
22
+
23
+ export const XML_METADATA_FORMAT = 'xml';
24
+ export const JSON_METADATA_FORMAT = 'json';
25
+
26
+ export const getSamlStartUrl = id => `${window.location.origin}${useradmApiUrl}/auth/sso/${id}/login`;
27
+ export const getOidcStartUrl = id => `${window.location.origin}${useradmApiUrl}/oidc/${id}/start`;
28
+
29
+ export const SSO_TYPES = {
30
+ saml: {
31
+ id: 'saml',
32
+ type: 'saml',
33
+ title: 'SAML',
34
+ metadataFormat: XML_METADATA_FORMAT,
35
+ editorLanguage: XML_METADATA_FORMAT,
36
+ contentType: 'application/samlmetadata+xml',
37
+ getStartUrl: getSamlStartUrl,
38
+ configDetails: [
39
+ { key: 'entityID', label: 'Entity ID', getValue: id => `${window.location.origin}${useradmApiUrl}/sso/sp/metadata/${id}` },
40
+ { key: 'acs', label: 'ACS URL', getValue: id => `${window.location.origin}${useradmApiUrl}/auth/sso/${id}/acs` },
41
+ { key: 'startURL', label: 'Start URL', getValue: getSamlStartUrl }
42
+ ]
43
+ },
44
+ oidc: {
45
+ id: 'oidc',
46
+ type: 'oidc',
47
+ title: 'OpenID Connect',
48
+ metadataFormat: JSON_METADATA_FORMAT,
49
+ editorLanguage: JSON_METADATA_FORMAT,
50
+ contentType: 'application/json',
51
+ getStartUrl: getOidcStartUrl,
52
+ configDetails: [{ key: 'startURL', label: 'Start Url', getValue: getOidcStartUrl }]
53
+ }
54
+ };
55
+
56
+ export const AUDIT_LOGS_TYPES = [
57
+ { title: 'Artifact', queryParameter: 'object_type', value: 'artifact' },
58
+ { title: 'Deployment', queryParameter: 'object_deployment_name', value: 'deployment' },
59
+ { title: 'Device', queryParameter: 'object_id', value: 'device' },
60
+ { title: 'User', queryParameter: 'object_id', value: 'user' }
61
+ ];
62
+
63
+ export interface Webhook extends Integration {
64
+ provider: Integration.provider.WEBHOOK;
65
+ scopes?: string[];
66
+ }
67
+
68
+ export const emptyWebhook: Webhook = {
69
+ description: '',
70
+ enabled: true,
71
+ credentials: {
72
+ type: EXTERNAL_PROVIDER.webhook.credentialsType,
73
+ [EXTERNAL_PROVIDER.webhook.credentialsType]: {
74
+ secret: '',
75
+ url: ''
76
+ }
77
+ }
78
+ };
79
+ export const TENANT_LIST_DEFAULT = {
80
+ page: 1,
81
+ perPage: 20
82
+ };
@@ -0,0 +1,111 @@
1
+ // Copyright 2023 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
+ import { DEVICE_LIST_DEFAULTS, SORTING_OPTIONS, TENANT_LIST_DEFAULT } from '@northern.tech/store/constants';
15
+ import { createSlice } from '@reduxjs/toolkit';
16
+
17
+ export const sliceName = 'organization';
18
+
19
+ export const initialState = {
20
+ card: {
21
+ last4: '',
22
+ expiration: { month: 1, year: 2020 },
23
+ brand: ''
24
+ },
25
+ intentId: null,
26
+ organization: {
27
+ tenantList: {
28
+ ...TENANT_LIST_DEFAULT,
29
+ total: 0,
30
+ tenants: [],
31
+ selectedTenant: null,
32
+ sort: {
33
+ direction: SORTING_OPTIONS.desc,
34
+ key: 'name'
35
+ }
36
+ }
37
+ // id, name, status, tenant_token, plan
38
+ },
39
+ auditlog: {
40
+ events: [],
41
+ selectionState: {
42
+ ...DEVICE_LIST_DEFAULTS,
43
+ detail: undefined,
44
+ endDate: undefined,
45
+ selectedIssue: undefined,
46
+ sort: { direction: SORTING_OPTIONS.desc },
47
+ startDate: undefined,
48
+ total: 0,
49
+ type: undefined,
50
+ user: undefined
51
+ }
52
+ },
53
+ externalDeviceIntegrations: [
54
+ // { <connection_string|x509|...>, id, provider }
55
+ ],
56
+ ssoConfigs: [],
57
+ webhooks: {
58
+ // [id]: { events: [] }
59
+ // for now:
60
+ events: [],
61
+ eventsTotal: 0
62
+ }
63
+ };
64
+
65
+ export const organizationSlice = createSlice({
66
+ name: sliceName,
67
+ initialState,
68
+ reducers: {
69
+ receiveAuditLogs: (state, action) => {
70
+ const { events, total } = action.payload;
71
+ state.auditlog.events = events;
72
+ state.auditlog.selectionState.total = total;
73
+ },
74
+ setAuditLogState: (state, action) => {
75
+ state.auditlog.selectionState = {
76
+ ...state.auditlog.selectionState,
77
+ ...action.payload
78
+ };
79
+ },
80
+ receiveCurrentCard: (state, action) => {
81
+ state.card = action.payload;
82
+ },
83
+ receiveSetupIntent: (state, action) => {
84
+ state.intentId = action.payload;
85
+ },
86
+ setOrganization: (state, action) => {
87
+ state.organization = {
88
+ ...state.organization,
89
+ ...action.payload,
90
+ tenantList: { ...state.organization.tenantList, ...action.payload.tenantList }
91
+ };
92
+ },
93
+ setTenantListState: (state, action) => {
94
+ state.organization.tenantList = action.payload;
95
+ },
96
+ receiveExternalDeviceIntegrations: (state, action) => {
97
+ state.externalDeviceIntegrations = action.payload;
98
+ },
99
+ receiveSsoConfigs: (state, action) => {
100
+ state.ssoConfigs = action.payload;
101
+ },
102
+ receiveWebhookEvents: (state, action) => {
103
+ const { value, total } = action.payload;
104
+ state.webhooks.events = value;
105
+ state.webhooks.eventsTotal = total;
106
+ }
107
+ }
108
+ });
109
+
110
+ export const actions = organizationSlice.actions;
111
+ export default organizationSlice.reducer;
@@ -0,0 +1,78 @@
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 { SORTING_OPTIONS } from '@northern.tech/store/commonConstants';
16
+
17
+ import reducer, { actions, initialState } from '.';
18
+ import { defaultState } from '../../../../tests/mockData';
19
+
20
+ describe('organization reducer', () => {
21
+ it('should return the initial state', async () => {
22
+ expect(reducer(undefined, {})).toEqual(initialState);
23
+ });
24
+
25
+ it('should handle RECEIVE_AUDIT_LOGS', async () => {
26
+ expect(
27
+ reducer(undefined, { type: actions.receiveAuditLogs, payload: { events: defaultState.organization.auditlog.events, total: 2 } }).auditlog.selectionState
28
+ .total
29
+ ).toEqual(2);
30
+ expect(
31
+ reducer(initialState, { type: actions.receiveAuditLogs, payload: { events: defaultState.organization.auditlog.events, total: 4 } }).auditlog
32
+ .selectionState.total
33
+ ).toEqual(4);
34
+ });
35
+ it('should handle SET_AUDITLOG_STATE', async () => {
36
+ const newState = { something: 'new' };
37
+ expect(reducer(undefined, { type: actions.setAuditLogState, payload: newState }).auditlog.selectionState.something).toEqual(newState.something);
38
+ expect(reducer(initialState, { type: actions.setAuditLogState, payload: newState }).auditlog.selectionState).toEqual({
39
+ ...defaultState.organization.auditlog.selectionState,
40
+ ...newState,
41
+ sort: { direction: SORTING_OPTIONS.desc },
42
+ total: 0
43
+ });
44
+ });
45
+ it('should handle RECEIVE_CURRENT_CARD', async () => {
46
+ expect(reducer(undefined, { type: actions.receiveCurrentCard, payload: defaultState.organization.card }).card).toEqual(defaultState.organization.card);
47
+ expect(reducer(initialState, { type: actions.receiveCurrentCard, payload: defaultState.organization.card }).card).toEqual(defaultState.organization.card);
48
+ });
49
+ it('should handle RECEIVE_SETUP_INTENT', async () => {
50
+ expect(reducer(undefined, { type: actions.receiveSetupIntent, payload: defaultState.organization.intentId }).intentId).toEqual(
51
+ defaultState.organization.intentId
52
+ );
53
+ expect(reducer(initialState, { type: actions.receiveSetupIntent, payload: 4 }).intentId).toEqual(4);
54
+ });
55
+ it('should handle SET_ORGANIZATION', async () => {
56
+ expect(reducer(undefined, { type: actions.setOrganization, payload: defaultState.organization.organization }).organization.plan).toEqual(
57
+ defaultState.organization.organization.plan
58
+ );
59
+ expect(reducer(initialState, { type: actions.setOrganization, payload: defaultState.organization.organization }).organization.name).toEqual(
60
+ defaultState.organization.organization.name
61
+ );
62
+ });
63
+ it('should handle RECEIVE_EXTERNAL_DEVICE_INTEGRATIONS', async () => {
64
+ expect(reducer(undefined, { type: actions.receiveExternalDeviceIntegrations, payload: [] }).externalDeviceIntegrations).toEqual([]);
65
+ expect(reducer(initialState, { type: actions.receiveExternalDeviceIntegrations, payload: [12, 23] }).externalDeviceIntegrations).toEqual([12, 23]);
66
+ });
67
+ it('should handle RECEIVE_WEBHOOK_EVENTS', async () => {
68
+ expect(reducer(undefined, { type: actions.receiveWebhookEvents, payload: { value: [] } }).webhooks.events).toEqual([]);
69
+ expect(reducer(initialState, { type: actions.receiveWebhookEvents, payload: { value: [12, 23], total: 5 } }).webhooks).toEqual({
70
+ events: [12, 23],
71
+ eventsTotal: 5
72
+ });
73
+ });
74
+ it('should handle RECEIVE_SSO_CONFIGS', async () => {
75
+ expect(reducer(undefined, { type: actions.receiveSsoConfigs, payload: [] }).ssoConfigs).toEqual([]);
76
+ expect(reducer(initialState, { type: actions.receiveSsoConfigs, payload: [12, 23] }).ssoConfigs).toEqual([12, 23]);
77
+ });
78
+ });