@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,1245 @@
1
+ // Copyright 2024 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
+
16
+ /*eslint import/namespace: ['error', { allowComputed: true }]*/
17
+ import React from 'react';
18
+ import { Link } from 'react-router-dom';
19
+
20
+ import { getSingleDeployment } from '@northern.tech/store/thunks';
21
+ import configureMockStore from 'redux-mock-store';
22
+ import { thunk } from 'redux-thunk';
23
+
24
+ import { actions } from '.';
25
+ import { inventoryDevice } from '../../../../tests/__mocks__/deviceHandlers';
26
+ import { defaultState } from '../../../../tests/mockData';
27
+ import { act, mockAbortController } from '../../../../tests/setupTests';
28
+ import { actions as appActions } from '../appSlice';
29
+ import { EXTERNAL_PROVIDER, UNGROUPED_GROUP } from '../constants';
30
+ import { actions as deploymentActions } from '../deploymentsSlice';
31
+ import { DEVICE_STATES } from './constants';
32
+ import {
33
+ addDevicesToGroup,
34
+ addDynamicGroup,
35
+ addStaticGroup,
36
+ applyDeviceConfig,
37
+ decommissionDevice,
38
+ deleteAuthset,
39
+ deriveInactiveDevices,
40
+ deriveReportsData,
41
+ deviceFileUpload,
42
+ getAllDeviceCounts,
43
+ getAllDevicesByStatus,
44
+ getAllDynamicGroupDevices,
45
+ getAllGroupDevices,
46
+ getDeviceAttributes,
47
+ getDeviceAuth,
48
+ getDeviceById,
49
+ getDeviceConfig,
50
+ getDeviceConnect,
51
+ getDeviceCount,
52
+ getDeviceFileDownloadLink,
53
+ getDeviceInfo,
54
+ getDeviceLimit,
55
+ getDeviceTwin,
56
+ getDevicesByStatus,
57
+ getDevicesWithAuth,
58
+ getDynamicGroups,
59
+ getGatewayDevices,
60
+ getGroupDevices,
61
+ getGroups,
62
+ getReportingLimits,
63
+ getReportsData,
64
+ getReportsDataWithoutBackendSupport,
65
+ getSessionDetails,
66
+ getSystemDevices,
67
+ preauthDevice,
68
+ removeDevicesFromGroup,
69
+ removeDynamicGroup,
70
+ removeStaticGroup,
71
+ selectGroup,
72
+ setDeviceConfig,
73
+ setDeviceListState,
74
+ setDeviceTags,
75
+ setDeviceTwin,
76
+ updateDeviceAuth,
77
+ updateDevicesAuth,
78
+ updateDynamicGroup
79
+ } from './thunks';
80
+
81
+ const middlewares = [thunk];
82
+ const mockStore = configureMockStore(middlewares);
83
+
84
+ const groupUpdateSuccessMessage = 'The group was updated successfully';
85
+ const getGroupSuccessNotification = groupName => (
86
+ <>
87
+ {groupUpdateSuccessMessage} - <Link to={`/devices?inventory=group:eq:${groupName}`}>click here</Link> to see it.
88
+ </>
89
+ );
90
+
91
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
92
+ const { attributes, check_in_time, updated_ts, ...expectedDevice } = defaultState.devices.byId.a1;
93
+ const receivedExpectedDevice = { type: actions.receivedDevices.type, payload: { [defaultState.devices.byId.a1.id]: expectedDevice } };
94
+ const defaultDeviceListState = {
95
+ type: actions.setDeviceListState.type,
96
+ payload: {
97
+ deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id],
98
+ isLoading: false,
99
+ total: 2
100
+ }
101
+ };
102
+ const acceptedDevices = {
103
+ type: actions.setDevicesByStatus.type,
104
+ payload: {
105
+ deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id],
106
+ status: DEVICE_STATES.accepted,
107
+ total: defaultState.devices.byStatus.accepted.total
108
+ }
109
+ };
110
+
111
+ const defaultResults = {
112
+ receivedDynamicGroups: {
113
+ type: actions.receivedGroups.type,
114
+ payload: {
115
+ testGroupDynamic: {
116
+ deviceIds: [],
117
+ filters: [
118
+ { key: 'id', operator: '$in', scope: 'identity', value: ['a1'] },
119
+ { key: 'mac', operator: '$nexists', scope: 'identity', value: false },
120
+ { key: 'kernel', operator: '$exists', scope: 'identity', value: true }
121
+ ],
122
+ id: 'filter1',
123
+ total: 0
124
+ }
125
+ }
126
+ },
127
+ addedUngroupedGroup: {
128
+ type: actions.addGroup.type,
129
+ payload: {
130
+ groupName: UNGROUPED_GROUP.id,
131
+ group: {
132
+ filters: [{ key: 'group', operator: '$nin', scope: 'system', value: [Object.keys(defaultState.devices.groups.byId)[0]] }]
133
+ }
134
+ }
135
+ },
136
+ receiveDefaultDevice: { type: actions.receivedDevices.type, payload: { [defaultState.devices.byId.a1.id]: defaultState.devices.byId.a1 } },
137
+ acceptedDevices,
138
+ receivedExpectedDevice,
139
+ defaultDeviceListState,
140
+ postDeviceAuthActions: [
141
+ { type: setDeviceListState.pending.type },
142
+ { type: getDevicesByStatus.pending.type },
143
+ { type: actions.setDeviceListState.type, payload: { deviceIds: [], isLoading: true, refreshTrigger: true } },
144
+ {
145
+ type: actions.receivedDevices.type,
146
+ payload: { [defaultState.devices.byId.a1.id]: { ...defaultState.devices.byId.a1, updated_ts: inventoryDevice.updated_ts } }
147
+ },
148
+ acceptedDevices,
149
+ { type: getDevicesWithAuth.pending.type },
150
+ receivedExpectedDevice,
151
+ { type: getDevicesWithAuth.fulfilled.type },
152
+ { type: getDevicesByStatus.fulfilled.type },
153
+ {
154
+ type: actions.setDeviceListState.type,
155
+ payload: { deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id], isLoading: false, total: 2 }
156
+ },
157
+ { type: setDeviceListState.fulfilled.type }
158
+ ]
159
+ };
160
+
161
+ /* eslint-disable sonarjs/no-identical-functions */
162
+ describe('selecting things', () => {
163
+ it('should allow device list selections', async () => {
164
+ const store = mockStore({ ...defaultState });
165
+ const expectedActions = [
166
+ { type: setDeviceListState.pending.type },
167
+ { type: getDevicesByStatus.pending.type },
168
+ { type: actions.setDeviceListState.type, payload: { deviceIds: ['a1'], isLoading: true } },
169
+ defaultResults.receivedExpectedDevice,
170
+ defaultResults.acceptedDevices,
171
+ { type: getDevicesWithAuth.pending.type },
172
+ defaultResults.receivedExpectedDevice,
173
+ { type: getDevicesWithAuth.fulfilled.type },
174
+ { type: getDevicesByStatus.fulfilled.type },
175
+ { type: actions.setDeviceListState.type, payload: { deviceIds: ['a1', 'b1'], isLoading: false } },
176
+ { type: setDeviceListState.fulfilled.type }
177
+ ];
178
+ await store.dispatch(setDeviceListState({ deviceIds: ['a1'] }));
179
+ const storeActions = store.getActions();
180
+ expect(storeActions.length).toEqual(expectedActions.length);
181
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
182
+ });
183
+ it('should allow device list selections without device retrieval', async () => {
184
+ const store = mockStore({ ...defaultState });
185
+ const expectedActions = [
186
+ { type: setDeviceListState.pending.type },
187
+ { type: actions.setDeviceListState.type, payload: { deviceIds: ['a1'], isLoading: false } },
188
+ { type: setDeviceListState.fulfilled.type }
189
+ ];
190
+ await store.dispatch(setDeviceListState({ deviceIds: ['a1'], setOnly: true }));
191
+ const storeActions = store.getActions();
192
+ expect(storeActions.length).toEqual(expectedActions.length);
193
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
194
+ });
195
+ it('should allow static group selection', async () => {
196
+ const store = mockStore({ ...defaultState });
197
+ const groupName = 'testGroup';
198
+ await store.dispatch(selectGroup({ group: groupName }));
199
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
200
+ const { attributes, updated_ts, ...expectedDevice } = defaultState.devices.byId.a1;
201
+ const expectedActions = [
202
+ { type: selectGroup.pending.type },
203
+ { type: actions.setDeviceFilters.type, payload: [] },
204
+ { type: getGroupDevices.pending.type },
205
+ { type: getDevicesByStatus.pending.type },
206
+ { type: actions.selectGroup.type, payload: groupName },
207
+ { type: actions.receivedDevices.type, payload: { [defaultState.devices.byId.a1.id]: { ...expectedDevice, attributes } } },
208
+ { type: getDevicesWithAuth.pending.type },
209
+ defaultResults.receivedExpectedDevice,
210
+ { type: getDevicesWithAuth.fulfilled.type },
211
+ { type: getDevicesByStatus.fulfilled.type },
212
+ {
213
+ type: actions.addGroup.type,
214
+ payload: { group: { deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id], total: 2 }, groupName }
215
+ },
216
+ { type: getGroupDevices.fulfilled.type },
217
+ { type: selectGroup.fulfilled.type }
218
+ ];
219
+ const storeActions = store.getActions();
220
+ expect(storeActions.length).toEqual(expectedActions.length);
221
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
222
+ });
223
+ it('should allow dynamic group selection', async () => {
224
+ const store = mockStore({ ...defaultState });
225
+ await store.dispatch(selectGroup({ group: 'testGroupDynamic' }));
226
+ const expectedActions = [
227
+ { type: selectGroup.pending.type },
228
+ { type: actions.setDeviceFilters.type, payload: [{ scope: 'system', key: 'group', operator: '$eq', value: 'things' }] },
229
+ { type: actions.selectGroup.type, payload: 'testGroupDynamic' },
230
+ { type: selectGroup.fulfilled.type }
231
+ ];
232
+ const storeActions = store.getActions();
233
+ expect(storeActions.length).toEqual(expectedActions.length);
234
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
235
+ });
236
+ it('should allow dynamic group selection with extra filters', async () => {
237
+ const store = mockStore({ ...defaultState });
238
+ const expectedActions = [
239
+ { type: selectGroup.pending.type },
240
+ {
241
+ type: actions.setDeviceFilters.type,
242
+ payload: [
243
+ { scope: 'system', key: 'group', operator: '$eq', value: 'things' },
244
+ { scope: 'system', key: 'group2', operator: '$eq', value: 'things2' }
245
+ ]
246
+ },
247
+ { type: actions.selectGroup.type, payload: 'testGroupDynamic' },
248
+ { type: selectGroup.fulfilled.type }
249
+ ];
250
+ await store.dispatch(
251
+ selectGroup({
252
+ group: 'testGroupDynamic',
253
+ filters: [...defaultState.devices.groups.byId.testGroupDynamic.filters, { scope: 'system', key: 'group2', operator: '$eq', value: 'things2' }]
254
+ })
255
+ );
256
+ const storeActions = store.getActions();
257
+ expect(storeActions.length).toEqual(expectedActions.length);
258
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
259
+ });
260
+ });
261
+
262
+ describe('overall device information retrieval', () => {
263
+ it('should allow count retrieval', async () => {
264
+ const store = mockStore({ ...defaultState });
265
+ const expectedActions = [
266
+ { type: getDeviceCount.pending.type },
267
+ { type: getDeviceCount.pending.type },
268
+ { type: getDeviceCount.pending.type },
269
+ { type: getDeviceCount.pending.type },
270
+ {
271
+ type: actions.setDevicesCountByStatus.type,
272
+ payload: { count: defaultState.devices.byStatus.accepted.total, status: DEVICE_STATES.accepted }
273
+ },
274
+ {
275
+ type: actions.setDevicesCountByStatus.type,
276
+ payload: { count: defaultState.devices.byStatus.pending.total, status: DEVICE_STATES.pending }
277
+ },
278
+ {
279
+ type: actions.setDevicesCountByStatus.type,
280
+ payload: { count: defaultState.devices.byStatus.preauthorized.total, status: DEVICE_STATES.preauth }
281
+ },
282
+ {
283
+ type: actions.setDevicesCountByStatus.type,
284
+ payload: { count: defaultState.devices.byStatus.rejected.total, status: DEVICE_STATES.rejected }
285
+ },
286
+ { type: getDeviceCount.fulfilled.type },
287
+ { type: getDeviceCount.fulfilled.type },
288
+ { type: getDeviceCount.fulfilled.type },
289
+ { type: getDeviceCount.fulfilled.type }
290
+ ];
291
+ await Promise.all(Object.values(DEVICE_STATES).map(status => store.dispatch(getDeviceCount(status)))).then(() => {
292
+ const storeActions = store.getActions();
293
+ expect(storeActions.length).toEqual(expectedActions.length);
294
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
295
+ });
296
+ });
297
+ it('should allow count retrieval for all state counts', async () => {
298
+ const store = mockStore({ ...defaultState });
299
+ const expectedActions = [
300
+ { type: getAllDeviceCounts.pending.type },
301
+ { type: getDeviceCount.pending.type },
302
+ { type: getDeviceCount.pending.type },
303
+ ...[DEVICE_STATES.accepted, DEVICE_STATES.pending].map(status => ({
304
+ type: actions.setDevicesCountByStatus.type,
305
+ payload: { count: defaultState.devices.byStatus[status].total, status }
306
+ })),
307
+ { type: getDeviceCount.fulfilled.type },
308
+ { type: getDeviceCount.fulfilled.type },
309
+ { type: getAllDeviceCounts.fulfilled.type }
310
+ ];
311
+ await store.dispatch(getAllDeviceCounts());
312
+ const storeActions = store.getActions();
313
+ expect(storeActions.length).toEqual(expectedActions.length);
314
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
315
+ });
316
+
317
+ it('should allow limit retrieval', async () => {
318
+ const store = mockStore({ ...defaultState });
319
+ const expectedActions = [
320
+ { type: getDeviceLimit.pending.type },
321
+ { type: actions.setDeviceLimit.type, payload: defaultState.devices.limit },
322
+ { type: getDeviceLimit.fulfilled.type }
323
+ ];
324
+ await store.dispatch(getDeviceLimit());
325
+ const storeActions = store.getActions();
326
+ expect(storeActions.length).toEqual(expectedActions.length);
327
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
328
+ });
329
+ it('should allow attribute retrieval and group results', async () => {
330
+ const store = mockStore({ ...defaultState });
331
+ const expectedActions = [
332
+ { type: getDeviceAttributes.pending.type },
333
+ { type: actions.setFilterAttributes.type, payload: {} },
334
+ { type: getDeviceAttributes.fulfilled.type }
335
+ ];
336
+ await store.dispatch(getDeviceAttributes());
337
+ const storeActions = store.getActions();
338
+ expect(storeActions.length).toEqual(expectedActions.length);
339
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
340
+ const receivedAttributes = storeActions.find(item => item.type === actions.setFilterAttributes.type).payload;
341
+ expect(Object.keys(receivedAttributes)).toHaveLength(4);
342
+ Object.entries(receivedAttributes).forEach(([key, value]) => {
343
+ expect(key).toBeTruthy();
344
+ expect(value).toBeTruthy();
345
+ });
346
+ });
347
+ it('should allow attribute config + limit retrieval and group results', async () => {
348
+ const store = mockStore({ ...defaultState });
349
+ const expectedActions = [
350
+ { type: getReportingLimits.pending.type },
351
+ {
352
+ type: actions.setFilterablesConfig.type,
353
+ payload: {
354
+ attributes: {
355
+ identity: ['status', 'mac'],
356
+ inventory: [
357
+ 'artifact_name',
358
+ 'cpu_model',
359
+ 'device_type',
360
+ 'hostname',
361
+ 'ipv4_wlan0',
362
+ 'ipv6_wlan0',
363
+ 'kernel',
364
+ 'mac_eth0',
365
+ 'mac_wlan0',
366
+ 'mem_total_kB',
367
+ 'mender_bootloader_integration',
368
+ 'mender_client_version',
369
+ 'network_interfaces',
370
+ 'os',
371
+ 'rootfs_type'
372
+ ],
373
+ system: ['created_ts', 'updated_ts', 'group']
374
+ },
375
+ count: 20,
376
+ limit: 100
377
+ }
378
+ },
379
+ { type: getReportingLimits.fulfilled.type }
380
+ ];
381
+ await store.dispatch(getReportingLimits());
382
+ const storeActions = store.getActions();
383
+ expect(storeActions.length).toEqual(expectedActions.length);
384
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
385
+ });
386
+
387
+ it('should allow getting device aggregation data for use in the dashboard/ reports', async () => {
388
+ const store = mockStore({
389
+ ...defaultState,
390
+ devices: { ...defaultState.devices, byStatus: { ...defaultState.devices.byStatus, accepted: { ...defaultState.devices.byStatus.accepted, total: 50 } } }
391
+ });
392
+ const expectedActions = [
393
+ { type: getReportsData.pending.type },
394
+ {
395
+ type: actions.setDeviceReports.type,
396
+ payload: [
397
+ {
398
+ items: [
399
+ { count: 6, key: 'test' },
400
+ { count: 1, key: 'original' }
401
+ ],
402
+ otherCount: 43,
403
+ total: 50
404
+ }
405
+ ]
406
+ },
407
+ { type: getReportsData.fulfilled.type }
408
+ ];
409
+ await store.dispatch(getReportsData());
410
+ const storeActions = store.getActions();
411
+ expect(storeActions.length).toEqual(expectedActions.length);
412
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
413
+ });
414
+ it('should allow getting device aggregation data for use in the dashboard/ reports even if the reporting service is not ready', async () => {
415
+ const groupName = 'testGroup';
416
+ const groupNameDynamic = 'testGroupDynamic';
417
+ const store = mockStore({
418
+ ...defaultState,
419
+ users: {
420
+ ...defaultState.users,
421
+ userSettings: {
422
+ ...defaultState.users.userSettings,
423
+ reports: [{ attribute: 'ipv4_wlan0', chartType: 'bar', group: groupName, type: 'distribution' }]
424
+ }
425
+ }
426
+ });
427
+ const expectedActions = [
428
+ { type: getReportsDataWithoutBackendSupport.pending.type },
429
+ { type: getAllDevicesByStatus.pending.type },
430
+ { type: getGroups.pending.type },
431
+ { type: getDynamicGroups.pending.type },
432
+ { type: actions.receivedGroups.type, payload: { testGroup: defaultState.devices.groups.byId.testGroup } },
433
+ { type: getDevicesByStatus.pending.type },
434
+ defaultResults.receivedDynamicGroups,
435
+ { type: getDynamicGroups.fulfilled.type },
436
+ defaultResults.receivedExpectedDevice,
437
+ defaultResults.acceptedDevices,
438
+ { type: deriveInactiveDevices.pending.type },
439
+ { type: actions.setInactiveDevices.type, payload: { activeDeviceTotal: 0, inactiveDeviceTotal: 2 } },
440
+ { type: deriveReportsData.pending.type },
441
+ { type: actions.setDeviceReports.type, payload: [{ items: [{ count: 2, key: '192.168.10.141/24' }], otherCount: 0, total: 2 }] },
442
+ { type: deriveInactiveDevices.fulfilled.type },
443
+ { type: deriveReportsData.fulfilled.type },
444
+ { type: getAllDevicesByStatus.fulfilled.type },
445
+ defaultResults.receiveDefaultDevice,
446
+ { type: getDevicesWithAuth.pending.type },
447
+ defaultResults.receivedExpectedDevice,
448
+ { type: getDevicesWithAuth.fulfilled.type },
449
+ { type: getDevicesByStatus.fulfilled.type },
450
+ defaultResults.addedUngroupedGroup,
451
+ { type: getGroups.fulfilled.type },
452
+ { type: getAllGroupDevices.pending.type },
453
+ { type: getAllDynamicGroupDevices.pending.type },
454
+ defaultResults.receivedExpectedDevice,
455
+ {
456
+ type: actions.addGroup.type,
457
+ payload: { group: { deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id], total: 2 }, groupName }
458
+ },
459
+ { type: actions.receivedDevices.type, payload: {} },
460
+ { type: actions.addGroup.type, payload: { group: { deviceIds: [], total: 0 }, groupName: groupNameDynamic } },
461
+ { type: getAllGroupDevices.fulfilled.type },
462
+ { type: getAllDynamicGroupDevices.fulfilled.type },
463
+ { type: deriveReportsData.pending.type },
464
+ { type: actions.setDeviceReports.type, payload: [{ items: [{ count: 2, key: '192.168.10.141/24' }], otherCount: 0, total: 2 }] },
465
+ { type: deriveReportsData.fulfilled.type },
466
+ { type: getReportsDataWithoutBackendSupport.fulfilled.type }
467
+ ];
468
+ await store.dispatch(getReportsDataWithoutBackendSupport());
469
+ const storeActions = store.getActions();
470
+ expect(storeActions.length).toEqual(expectedActions.length);
471
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
472
+ });
473
+ it('should allow system devices retrieval', async () => {
474
+ const store = mockStore({
475
+ ...defaultState,
476
+ app: {
477
+ ...defaultState.app,
478
+ features: {
479
+ ...defaultState.app.features,
480
+ isEnterprise: true
481
+ }
482
+ }
483
+ });
484
+ const expectedActions = [
485
+ { type: getSystemDevices.pending.type },
486
+ {
487
+ type: actions.receivedDevices.type,
488
+ payload: {
489
+ [defaultState.devices.byId.a1.id]: {
490
+ ...defaultState.devices.byId.a1,
491
+ systemDeviceIds: [],
492
+ systemDeviceTotal: 0
493
+ }
494
+ }
495
+ },
496
+ { type: getSystemDevices.fulfilled.type }
497
+ ];
498
+ await store.dispatch(getSystemDevices({ id: defaultState.devices.byId.a1.id }));
499
+ const storeActions = store.getActions();
500
+ expect(storeActions.length).toEqual(expectedActions.length);
501
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
502
+ });
503
+ it('should allow system devices retrieval', async () => {
504
+ const gatewayDevice = defaultState.devices.byId.a1;
505
+ const store = mockStore({
506
+ ...defaultState,
507
+ app: {
508
+ ...defaultState.app,
509
+ features: {
510
+ ...defaultState.app.features,
511
+ isEnterprise: true
512
+ }
513
+ },
514
+ devices: {
515
+ ...defaultState.devices,
516
+ byId: {
517
+ ...defaultState.devices.byId,
518
+ [gatewayDevice.id]: {
519
+ ...gatewayDevice,
520
+ attributes: {
521
+ ...gatewayDevice.attributes,
522
+ mender_gateway_system_id: 'gatewaySystem'
523
+ }
524
+ }
525
+ }
526
+ }
527
+ });
528
+ const expectedActions = [
529
+ { type: getGatewayDevices.pending.type },
530
+ { type: actions.receivedDevice.type, payload: { id: gatewayDevice.id, gatewayIds: [] } },
531
+ { type: getGatewayDevices.fulfilled.type }
532
+ ];
533
+ await store.dispatch(getGatewayDevices(defaultState.devices.byId.a1.id));
534
+ const storeActions = store.getActions();
535
+ expect(storeActions.length).toEqual(expectedActions.length);
536
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
537
+ });
538
+ });
539
+
540
+ describe('device auth handling', () => {
541
+ const deviceUpdateSuccessMessage = 'Device authorization status was updated successfully';
542
+ it('should allow device auth information retrieval', async () => {
543
+ const store = mockStore({ ...defaultState });
544
+ const expectedActions = [
545
+ { type: getDeviceAuth.pending.type },
546
+ { type: getDevicesWithAuth.pending.type },
547
+ defaultResults.receivedExpectedDevice,
548
+ { type: getDevicesWithAuth.fulfilled.type },
549
+ { type: getDeviceAuth.fulfilled.type }
550
+ ];
551
+ await store.dispatch(getDeviceAuth(defaultState.devices.byId.a1.id));
552
+ const storeActions = store.getActions();
553
+ expect(storeActions.length).toEqual(expectedActions.length);
554
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
555
+ });
556
+ it('should return device auth device as a promise result', async () => {
557
+ const store = mockStore({ ...defaultState });
558
+ const device = await store.dispatch(getDeviceAuth(defaultState.devices.byId.a1.id));
559
+ expect(device).toBeDefined();
560
+ });
561
+ it('should allow single device auth updates', async () => {
562
+ const store = mockStore({ ...defaultState });
563
+ const expectedActions = [
564
+ { type: updateDeviceAuth.pending.type },
565
+ { type: getDeviceAuth.pending.type },
566
+ { type: getDevicesWithAuth.pending.type },
567
+ { type: appActions.setSnackbar.type, payload: deviceUpdateSuccessMessage },
568
+ defaultResults.receivedExpectedDevice,
569
+ { type: getDevicesWithAuth.fulfilled.type },
570
+ { type: getDeviceAuth.fulfilled.type },
571
+ { type: actions.maybeUpdateDevicesByStatus.type },
572
+ { type: setDeviceListState.pending.type },
573
+ { type: getDevicesByStatus.pending.type },
574
+ { type: actions.setDeviceListState.type, payload: { deviceIds: [], isLoading: true, refreshTrigger: true } },
575
+ {
576
+ type: actions.receivedDevices.type,
577
+ payload: { [defaultState.devices.byId.a1.id]: { ...defaultState.devices.byId.a1, updated_ts: inventoryDevice.updated_ts } }
578
+ },
579
+ acceptedDevices,
580
+ { type: getDevicesWithAuth.pending.type },
581
+ receivedExpectedDevice,
582
+ { type: getDevicesWithAuth.fulfilled.type },
583
+ { type: getDevicesByStatus.fulfilled.type },
584
+ {
585
+ type: actions.setDeviceListState.type,
586
+ payload: { deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id], total: 2, isLoading: false }
587
+ },
588
+ { type: setDeviceListState.fulfilled.type },
589
+ { type: updateDeviceAuth.fulfilled.type }
590
+ ];
591
+ await store.dispatch(
592
+ updateDeviceAuth({ deviceId: defaultState.devices.byId.a1.id, authId: defaultState.devices.byId.a1.auth_sets[0].id, status: DEVICE_STATES.pending })
593
+ );
594
+ const storeActions = store.getActions();
595
+ expect(storeActions.length).toEqual(expectedActions.length);
596
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
597
+ });
598
+ it('should allow multiple device auth updates', async () => {
599
+ const store = mockStore({ ...defaultState });
600
+ const expectedActions = [
601
+ { type: updateDevicesAuth.pending.type },
602
+ { type: getDevicesWithAuth.pending.type },
603
+ { type: getDevicesWithAuth.fulfilled.type },
604
+ { type: updateDeviceAuth.pending.type },
605
+ { type: getDeviceAuth.pending.type },
606
+ { type: getDevicesWithAuth.pending.type },
607
+ { type: appActions.setSnackbar.type, payload: deviceUpdateSuccessMessage },
608
+ defaultResults.receivedExpectedDevice,
609
+ { type: getDevicesWithAuth.fulfilled.type },
610
+ { type: getDeviceAuth.fulfilled.type },
611
+ { type: actions.maybeUpdateDevicesByStatus.type },
612
+ { type: setDeviceListState.pending.type },
613
+ { type: getDevicesByStatus.pending.type },
614
+ { type: actions.setDeviceListState.type, payload: { deviceIds: [], total: 0, isLoading: true } },
615
+ receivedExpectedDevice,
616
+ defaultResults.acceptedDevices,
617
+ { type: getDevicesWithAuth.pending.type },
618
+ receivedExpectedDevice,
619
+ { type: getDevicesWithAuth.fulfilled.type },
620
+ { type: getDevicesByStatus.fulfilled.type },
621
+ {
622
+ type: actions.setDeviceListState.type,
623
+ payload: { deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id], total: 2, isLoading: false }
624
+ },
625
+ { type: setDeviceListState.fulfilled.type },
626
+ { type: updateDeviceAuth.fulfilled.type },
627
+ {
628
+ type: appActions.setSnackbar.type,
629
+ payload:
630
+ '1 device was updated successfully. 1 device has more than one pending authset. Expand this device to individually adjust its authorization status. '
631
+ },
632
+ { type: updateDevicesAuth.fulfilled.type }
633
+ ];
634
+ await store.dispatch(updateDevicesAuth({ deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.c1.id], status: DEVICE_STATES.pending }));
635
+ await act(async () => {
636
+ jest.runOnlyPendingTimers();
637
+ jest.runAllTicks();
638
+ });
639
+ const storeActions = store.getActions();
640
+ expect(storeActions.length).toEqual(expectedActions.length);
641
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
642
+ });
643
+ it('should allow preauthorizing devices', async () => {
644
+ const store = mockStore({ ...defaultState });
645
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
646
+ const expectedActions = [
647
+ { type: preauthDevice.pending.type },
648
+ { type: appActions.setSnackbar.type, payload: 'Device was successfully added to the preauthorization list' },
649
+ { type: preauthDevice.fulfilled.type }
650
+ ];
651
+ await store.dispatch(
652
+ preauthDevice({
653
+ ...defaultState.devices.byId.a1.auth_sets[0],
654
+ identity_data: { ...defaultState.devices.byId.a1.auth_sets[0].identity_data, mac: '12:34:56' },
655
+ pubkey: 'test'
656
+ })
657
+ );
658
+ const storeActions = store.getActions();
659
+ expect(storeActions.length).toEqual(expectedActions.length);
660
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
661
+ });
662
+ it('should notify about duplicate device preauthorization attempts', async () => {
663
+ const store = mockStore({ ...defaultState });
664
+ await store
665
+ .dispatch(preauthDevice(defaultState.devices.byId.a1.auth_sets[0]))
666
+ .unwrap()
667
+ .catch(({ message }) => expect(message).toContain('identity data set already exists'));
668
+ });
669
+ it('should allow single device auth set deletion', async () => {
670
+ const store = mockStore({ ...defaultState });
671
+ const expectedActions = [
672
+ { type: deleteAuthset.pending.type },
673
+ { type: appActions.setSnackbar.type, payload: deviceUpdateSuccessMessage },
674
+ { type: actions.maybeUpdateDevicesByStatus.type },
675
+ ...defaultResults.postDeviceAuthActions,
676
+ { type: deleteAuthset.fulfilled.type }
677
+ ];
678
+ await store.dispatch(deleteAuthset({ deviceId: defaultState.devices.byId.a1.id, authId: defaultState.devices.byId.a1.auth_sets[0].id }));
679
+ const storeActions = store.getActions();
680
+ expect(storeActions.length).toEqual(expectedActions.length);
681
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
682
+ });
683
+ it('should allow single device decomissioning', async () => {
684
+ const store = mockStore({ ...defaultState });
685
+ const expectedActions = [
686
+ { type: decommissionDevice.pending.type },
687
+ { type: appActions.setSnackbar.type, payload: 'Device was decommissioned successfully' },
688
+ { type: actions.maybeUpdateDevicesByStatus.type },
689
+ ...defaultResults.postDeviceAuthActions,
690
+ { type: decommissionDevice.fulfilled.type }
691
+ ];
692
+ await store.dispatch(decommissionDevice({ deviceId: defaultState.devices.byId.a1.id }));
693
+ const storeActions = store.getActions();
694
+ expect(storeActions.length).toEqual(expectedActions.length);
695
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
696
+ });
697
+ });
698
+
699
+ describe('static grouping related actions', () => {
700
+ it('should allow retrieving static groups', async () => {
701
+ const store = mockStore({ ...defaultState });
702
+ const expectedActions = [
703
+ { type: getGroups.pending.type },
704
+ { type: actions.receivedGroups.type, payload: { testGroup: defaultState.devices.groups.byId.testGroup } },
705
+ { type: getDevicesByStatus.pending.type },
706
+ {
707
+ type: actions.receivedDevices.type,
708
+ payload: { [defaultState.devices.byId.a1.id]: { ...defaultState.devices.byId.a1, updated_ts: inventoryDevice.updated_ts } }
709
+ },
710
+ { type: getDevicesWithAuth.pending.type },
711
+ defaultResults.receiveDefaultDevice,
712
+ { type: getDevicesWithAuth.fulfilled.type },
713
+ { type: getDevicesByStatus.fulfilled.type },
714
+ defaultResults.addedUngroupedGroup,
715
+ { type: getGroups.fulfilled.type }
716
+ ];
717
+ await store.dispatch(getGroups());
718
+ const storeActions = store.getActions();
719
+ expect(storeActions.length).toEqual(expectedActions.length);
720
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
721
+ });
722
+ it('should allow creating static groups', async () => {
723
+ const store = mockStore({ ...defaultState });
724
+ const groupName = 'createdTestGroup';
725
+ const expectedActions = [
726
+ { type: addStaticGroup.pending.type },
727
+ { type: addDevicesToGroup.pending.type },
728
+ { type: actions.addToGroup.type, payload: { group: groupName, deviceIds: [defaultState.devices.byId.a1.id] } },
729
+ { type: getGroups.pending.type },
730
+ { type: actions.receivedGroups.type, payload: { testGroup: defaultState.devices.groups.byId.testGroup } },
731
+ { type: getDevicesByStatus.pending.type },
732
+ {
733
+ type: actions.receivedDevices.type,
734
+ payload: { [defaultState.devices.byId.a1.id]: { ...defaultState.devices.byId.a1, updated_ts: inventoryDevice.updated_ts } }
735
+ },
736
+ { type: getDevicesWithAuth.pending.type },
737
+ defaultResults.receiveDefaultDevice,
738
+ { type: getDevicesWithAuth.fulfilled.type },
739
+ { type: getDevicesByStatus.fulfilled.type },
740
+ defaultResults.addedUngroupedGroup,
741
+ { type: getGroups.fulfilled.type },
742
+ { type: addDevicesToGroup.fulfilled.type },
743
+ { type: actions.addGroup.type, payload: { groupName, group: { deviceIds: [], total: 0, filters: [] } } },
744
+ { type: setDeviceListState.pending.type },
745
+ { type: actions.setDeviceListState.type, payload: { ...defaultState.devices.deviceList, deviceIds: [], setOnly: true } },
746
+ { type: getGroups.pending.type },
747
+ { type: appActions.setSnackbar.type, payload: getGroupSuccessNotification(groupName) },
748
+ { type: setDeviceListState.fulfilled.type },
749
+ { type: actions.receivedGroups.type, payload: { testGroup: defaultState.devices.groups.byId.testGroup } },
750
+ { type: getDevicesByStatus.pending.type },
751
+ defaultResults.receiveDefaultDevice,
752
+ { type: getDevicesWithAuth.pending.type },
753
+ {
754
+ type: actions.receivedDevices.type,
755
+ payload: { [defaultState.devices.byId.a1.id]: { ...defaultState.devices.byId.a1, updated_ts: inventoryDevice.updated_ts } }
756
+ },
757
+ { type: getDevicesWithAuth.fulfilled.type },
758
+ { type: getDevicesByStatus.fulfilled.type },
759
+ defaultResults.addedUngroupedGroup,
760
+ { type: getGroups.fulfilled.type },
761
+ { type: addStaticGroup.fulfilled.type }
762
+ ];
763
+ await store.dispatch(addStaticGroup({ group: groupName, devices: [defaultState.devices.byId.a1] }));
764
+ const storeActions = store.getActions();
765
+ expect(storeActions.length).toEqual(expectedActions.length);
766
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
767
+ });
768
+ it('should allow extending static groups', async () => {
769
+ const store = mockStore({ ...defaultState });
770
+ const groupName = 'createdTestGroup';
771
+ const expectedActions = [
772
+ { type: addDevicesToGroup.pending.type },
773
+ { type: actions.addToGroup.type, payload: { group: groupName, deviceIds: [defaultState.devices.byId.b1.id] } },
774
+ { type: addDevicesToGroup.fulfilled.type }
775
+ ];
776
+ await store.dispatch(addDevicesToGroup({ group: groupName, deviceIds: [defaultState.devices.byId.b1.id] }));
777
+ const storeActions = store.getActions();
778
+ expect(storeActions.length).toEqual(expectedActions.length);
779
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
780
+ });
781
+ it('should allow shrinking static groups', async () => {
782
+ const store = mockStore({ ...defaultState });
783
+ const groupName = 'testGroup';
784
+ const expectedActions = [
785
+ { type: removeDevicesFromGroup.pending.type },
786
+ { type: actions.removeFromGroup.type, payload: { group: groupName, deviceIds: [defaultState.devices.byId.b1.id] } },
787
+ { type: appActions.setSnackbar.type, payload: 'The device was removed from the group' },
788
+ { type: removeDevicesFromGroup.fulfilled.type }
789
+ ];
790
+ await store.dispatch(removeDevicesFromGroup({ group: groupName, deviceIds: [defaultState.devices.byId.b1.id] }));
791
+ const storeActions = store.getActions();
792
+ expect(storeActions.length).toEqual(expectedActions.length);
793
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
794
+ });
795
+ it('should allow removing static groups', async () => {
796
+ const store = mockStore({ ...defaultState });
797
+ const groupName = 'testGroup';
798
+ const expectedActions = [
799
+ { type: removeStaticGroup.pending.type },
800
+ { type: actions.removeGroup.type, payload: groupName },
801
+ { type: getGroups.pending.type },
802
+ { type: appActions.setSnackbar.type, payload: 'Group was removed successfully' },
803
+ { type: actions.receivedGroups.type, payload: { testGroup: defaultState.devices.groups.byId.testGroup } },
804
+ { type: getDevicesByStatus.pending.type },
805
+ {
806
+ type: actions.receivedDevices.type,
807
+ payload: { [defaultState.devices.byId.a1.id]: { ...defaultState.devices.byId.a1, updated_ts: inventoryDevice.updated_ts } }
808
+ },
809
+ { type: getDevicesWithAuth.pending.type },
810
+ defaultResults.receiveDefaultDevice,
811
+ { type: getDevicesWithAuth.fulfilled.type },
812
+ { type: getDevicesByStatus.fulfilled.type },
813
+ defaultResults.addedUngroupedGroup,
814
+ { type: getGroups.fulfilled.type },
815
+ { type: removeStaticGroup.fulfilled.type }
816
+ ];
817
+ await store.dispatch(removeStaticGroup(groupName));
818
+ const storeActions = store.getActions();
819
+ expect(storeActions.length).toEqual(expectedActions.length);
820
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
821
+ });
822
+ it('should allow device retrieval for static groups', async () => {
823
+ const store = mockStore({ ...defaultState });
824
+ const groupName = 'testGroup';
825
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
826
+ const { attributes, updated_ts, ...expectedDevice } = defaultState.devices.byId.a1;
827
+ const expectedActions = [
828
+ { type: getGroupDevices.pending.type },
829
+ { type: getDevicesByStatus.pending.type },
830
+ { type: actions.receivedDevices.type, payload: { [defaultState.devices.byId.a1.id]: { ...expectedDevice, attributes } } },
831
+ {
832
+ type: actions.setDevicesByStatus.type,
833
+ payload: { deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id], status: DEVICE_STATES.accepted, total: 2 }
834
+ },
835
+ { type: getDevicesWithAuth.pending.type },
836
+ { type: actions.receivedDevices.type, payload: { [expectedDevice.id]: { ...expectedDevice, updated_ts } } },
837
+ { type: getDevicesWithAuth.fulfilled.type },
838
+ { type: getDevicesByStatus.fulfilled.type },
839
+ { type: getGroupDevices.fulfilled.type }
840
+ ];
841
+ await store.dispatch(getGroupDevices(groupName));
842
+ const storeActions = store.getActions();
843
+ expect(storeActions.length).toEqual(expectedActions.length);
844
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
845
+ const devicesById = storeActions.find(item => item.type === actions.receivedDevices.type).payload;
846
+ expect(devicesById[defaultState.devices.byId.a1.id]).toBeTruthy();
847
+ expect(new Date(devicesById[defaultState.devices.byId.a1.id].updated_ts).getTime()).toBeGreaterThanOrEqual(new Date(updated_ts).getTime());
848
+ });
849
+ it('should allow complete device retrieval for static groups', async () => {
850
+ const store = mockStore({ ...defaultState });
851
+ const groupName = 'testGroup';
852
+ const expectedActions = [
853
+ { type: getAllGroupDevices.pending.type },
854
+ defaultResults.receivedExpectedDevice,
855
+ {
856
+ type: actions.addGroup.type,
857
+ payload: { group: { deviceIds: [defaultState.devices.byId.a1.id, defaultState.devices.byId.b1.id], total: 2 }, groupName }
858
+ },
859
+ { type: getAllGroupDevices.fulfilled.type }
860
+ ];
861
+ await store.dispatch(getAllGroupDevices(groupName));
862
+ const storeActions = store.getActions();
863
+ expect(storeActions.length).toEqual(expectedActions.length);
864
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
865
+ });
866
+ });
867
+
868
+ describe('dynamic grouping related actions', () => {
869
+ it('should allow retrieving dynamic groups', async () => {
870
+ const store = mockStore({ ...defaultState });
871
+ const expectedActions = [{ type: getDynamicGroups.pending.type }, defaultResults.receivedDynamicGroups, { type: getDynamicGroups.fulfilled.type }];
872
+ await store.dispatch(getDynamicGroups());
873
+ const storeActions = store.getActions();
874
+ expect(storeActions.length).toEqual(expectedActions.length);
875
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
876
+ });
877
+
878
+ it('should allow creating dynamic groups', async () => {
879
+ const store = mockStore({ ...defaultState });
880
+ const groupName = 'createdTestGroup';
881
+ const expectedActions = [
882
+ { type: addDynamicGroup.pending.type },
883
+ {
884
+ type: actions.addGroup.type,
885
+ payload: { groupName, group: { filters: [{ key: 'group', operator: '$nin', scope: 'system', value: ['testGroup'] }] } }
886
+ },
887
+ { type: actions.setDeviceFilters.type, payload: [] },
888
+ { type: appActions.setSnackbar.type, payload: getGroupSuccessNotification(groupName) },
889
+ { type: getDynamicGroups.pending.type },
890
+ defaultResults.receivedDynamicGroups,
891
+ { type: getDynamicGroups.fulfilled.type },
892
+ { type: addDynamicGroup.fulfilled.type }
893
+ ];
894
+ await store.dispatch(addDynamicGroup({ groupName, filterPredicates: [{ key: 'group', operator: '$nin', scope: 'system', value: ['testGroup'] }] }));
895
+ const storeActions = store.getActions();
896
+ expect(storeActions.length).toEqual(expectedActions.length);
897
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
898
+ });
899
+ it('should allow complete device retrieval for dynamic groups', async () => {
900
+ const store = mockStore({ ...defaultState });
901
+ const groupName = 'testGroupDynamic';
902
+ const expectedActions = [
903
+ { type: getAllDynamicGroupDevices.pending.type },
904
+ { type: actions.receivedDevices.type, payload: {} },
905
+ { type: actions.addGroup.type, payload: { group: { deviceIds: [], total: 0 }, groupName } },
906
+ { type: getAllDynamicGroupDevices.fulfilled.type }
907
+ ];
908
+ await store.dispatch(getAllDynamicGroupDevices(groupName));
909
+ const storeActions = store.getActions();
910
+ expect(storeActions.length).toEqual(expectedActions.length);
911
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
912
+ });
913
+ it('should allow dynamic group updates', async () => {
914
+ const groupName = 'testGroupDynamic';
915
+ const store = mockStore({
916
+ ...defaultState,
917
+ devices: {
918
+ ...defaultState.devices,
919
+ groups: {
920
+ ...defaultState.devices.groups,
921
+ selectedGroup: groupName
922
+ }
923
+ }
924
+ });
925
+ const expectedActions = [
926
+ { type: updateDynamicGroup.pending.type },
927
+ { type: addDynamicGroup.pending.type },
928
+ { type: actions.addGroup.type, payload: { groupName, group: { filters: [] } } },
929
+ { type: actions.setDeviceFilters.type, payload: defaultState.devices.groups.byId.testGroupDynamic.filters },
930
+ { type: appActions.setSnackbar.type, payload: groupUpdateSuccessMessage },
931
+ { type: getDynamicGroups.pending.type },
932
+ defaultResults.receivedDynamicGroups,
933
+ { type: getDynamicGroups.fulfilled.type },
934
+ { type: addDynamicGroup.fulfilled.type },
935
+ { type: updateDynamicGroup.fulfilled.type }
936
+ ];
937
+ await store.dispatch(updateDynamicGroup({ groupName, filterPredicates: [] }));
938
+ const storeActions = store.getActions();
939
+ expect(storeActions.length).toEqual(expectedActions.length);
940
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
941
+ });
942
+ it('should allow removing dynamic groups', async () => {
943
+ const store = mockStore({ ...defaultState });
944
+ const groupName = 'testGroupDynamic';
945
+ const expectedActions = [
946
+ { type: removeDynamicGroup.pending.type },
947
+ { type: actions.removeGroup.type, payload: groupName },
948
+ { type: appActions.setSnackbar.type, payload: 'Group was removed successfully' },
949
+ { type: removeDynamicGroup.fulfilled.type }
950
+ ];
951
+ await store.dispatch(removeDynamicGroup(groupName));
952
+ const storeActions = store.getActions();
953
+ expect(storeActions.length).toEqual(expectedActions.length);
954
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
955
+ });
956
+ });
957
+
958
+ describe('device retrieval ', () => {
959
+ it('should allow single device retrieval from inventory', async () => {
960
+ const store = mockStore({
961
+ ...defaultState
962
+ });
963
+ const { attributes, id } = defaultState.devices.byId.a1;
964
+ const expectedActions = [
965
+ { type: getDeviceById.pending.type },
966
+ { type: actions.receivedDevice.type, payload: { attributes, id } },
967
+ { type: getDeviceById.fulfilled.type }
968
+ ];
969
+ await store.dispatch(getDeviceById(defaultState.devices.byId.a1.id));
970
+ const storeActions = store.getActions();
971
+ expect(storeActions.length).toEqual(expectedActions.length);
972
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
973
+ });
974
+ it('should allow single device retrieval from detailed sources', async () => {
975
+ const store = mockStore({
976
+ ...defaultState,
977
+ app: { ...defaultState.app, features: { ...defaultState.app.features, hasDeviceConnect: true } },
978
+ organization: { ...defaultState.organization, addons: [], externalDeviceIntegrations: [{ ...EXTERNAL_PROVIDER['iot-hub'], id: 'test' }] }
979
+ });
980
+ const { attributes, updated_ts, id, ...expectedDevice } = defaultState.devices.byId.a1;
981
+ const expectedActions = [
982
+ { type: getDeviceInfo.pending.type },
983
+ { type: getDeviceAuth.pending.type },
984
+ { type: getDevicesWithAuth.pending.type },
985
+ { type: getDeviceTwin.pending.type },
986
+ { type: getDeviceById.pending.type },
987
+ { type: getDeviceConnect.pending.type },
988
+ { type: actions.receivedDevices.type, payload: { [id]: { ...expectedDevice, id } } },
989
+ { type: actions.receivedDevice.type, payload: { attributes, id } },
990
+ { type: getDevicesWithAuth.fulfilled.type },
991
+ { type: getDeviceById.fulfilled.type },
992
+ { type: getDeviceAuth.fulfilled.type },
993
+ { type: actions.receivedDevice.type, payload: { connect_status: 'connected', connect_updated_ts: updated_ts, id } },
994
+ { type: actions.receivedDevice.type, payload: expectedDevice },
995
+ { type: getDeviceConnect.fulfilled.type },
996
+ { type: getDeviceTwin.fulfilled.type },
997
+ { type: getDeviceInfo.fulfilled.type }
998
+ ];
999
+ await store.dispatch(getDeviceInfo(defaultState.devices.byId.a1.id));
1000
+ const storeActions = store.getActions();
1001
+ expect(storeActions.length).toEqual(expectedActions.length);
1002
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1003
+ });
1004
+ it('should allow retrieving multiple devices by status', async () => {
1005
+ const store = mockStore({ ...defaultState });
1006
+ const expectedActions = [
1007
+ { type: getDevicesByStatus.pending.type },
1008
+ defaultResults.receivedExpectedDevice,
1009
+ defaultResults.acceptedDevices,
1010
+ { type: getDevicesWithAuth.pending.type },
1011
+ defaultResults.receivedExpectedDevice,
1012
+ { type: getDevicesWithAuth.fulfilled.type },
1013
+ { type: getDevicesByStatus.fulfilled.type }
1014
+ ];
1015
+ await store.dispatch(getDevicesByStatus({ status: DEVICE_STATES.accepted }));
1016
+ const storeActions = store.getActions();
1017
+ expect(storeActions.length).toEqual(expectedActions.length);
1018
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1019
+ });
1020
+ it('should allow retrieving multiple devices by status and select if requested', async () => {
1021
+ const store = mockStore({ ...defaultState });
1022
+ const expectedActions = [
1023
+ { type: getDevicesByStatus.pending.type },
1024
+ defaultResults.receivedExpectedDevice,
1025
+ {
1026
+ type: actions.setDevicesByStatus.type,
1027
+ payload: { deviceIds: [defaultState.devices.byId.a1.id], status: DEVICE_STATES.accepted, total: defaultState.devices.byStatus.accepted.total }
1028
+ },
1029
+ { type: getDevicesWithAuth.pending.type },
1030
+ defaultResults.receivedExpectedDevice,
1031
+ { type: getDevicesWithAuth.fulfilled.type },
1032
+ { type: getDevicesByStatus.fulfilled.type }
1033
+ ];
1034
+ await store.dispatch(getDevicesByStatus({ status: DEVICE_STATES.accepted, perPage: 1, shouldSelectDevices: true }));
1035
+ const storeActions = store.getActions();
1036
+ expect(storeActions.length).toEqual(expectedActions.length);
1037
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1038
+ });
1039
+ it('should allow retrieving devices based on devicelist state', async () => {
1040
+ const store = mockStore({ ...defaultState });
1041
+ const expectedActions = [
1042
+ { type: setDeviceListState.pending.type },
1043
+ { type: getDevicesByStatus.pending.type },
1044
+ { type: actions.setDeviceListState.type, payload: { ...defaultState.devices.deviceList, perPage: 2, deviceIds: [], isLoading: true } },
1045
+ defaultResults.receivedExpectedDevice,
1046
+ defaultResults.acceptedDevices,
1047
+ { type: getDevicesWithAuth.pending.type },
1048
+ defaultResults.receivedExpectedDevice,
1049
+ { type: getDevicesWithAuth.fulfilled.type },
1050
+ { type: getDevicesByStatus.fulfilled.type },
1051
+ // the following perPage setting should be 2 as well, but the test backend seems to respond too fast for the state change to propagate
1052
+ defaultResults.defaultDeviceListState,
1053
+ { type: setDeviceListState.fulfilled.type }
1054
+ ];
1055
+ await store.dispatch(setDeviceListState({ page: 1, perPage: 2, refreshTrigger: true }));
1056
+ const storeActions = store.getActions();
1057
+ expect(storeActions.length).toEqual(expectedActions.length);
1058
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1059
+ });
1060
+ it('should allow retrieving all devices per status', async () => {
1061
+ const store = mockStore({ ...defaultState });
1062
+ const expectedActions = [
1063
+ { type: getAllDevicesByStatus.pending.type },
1064
+ defaultResults.receivedExpectedDevice,
1065
+ defaultResults.acceptedDevices,
1066
+ { type: deriveInactiveDevices.pending.type },
1067
+ { type: actions.setInactiveDevices.type, payload: { activeDeviceTotal: 0, inactiveDeviceTotal: 2 } },
1068
+ { type: deriveReportsData.pending.type },
1069
+ { type: actions.setDeviceReports.type, payload: [{ items: [{ count: 2, key: 'undefined' }], otherCount: 0, total: 2 }] },
1070
+ { type: deriveInactiveDevices.fulfilled.type },
1071
+ { type: deriveReportsData.fulfilled.type },
1072
+ { type: getAllDevicesByStatus.fulfilled.type }
1073
+ ];
1074
+ await store.dispatch(getAllDevicesByStatus(DEVICE_STATES.accepted));
1075
+ const storeActions = store.getActions();
1076
+ expect(storeActions.length).toEqual(expectedActions.length);
1077
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1078
+ });
1079
+ it('should allow retrieving devices per status and their auth data', async () => {
1080
+ const store = mockStore({ ...defaultState });
1081
+ const {
1082
+ a1: { attributes: attributes1, ...expectedDevice1 }, // eslint-disable-line @typescript-eslint/no-unused-vars
1083
+ b1: { attributes: attributes2, auth_sets, ...expectedDevice2 } // eslint-disable-line @typescript-eslint/no-unused-vars
1084
+ } = defaultState.devices.byId;
1085
+ const expectedActions = [
1086
+ { type: getDevicesWithAuth.pending.type },
1087
+ { type: actions.receivedDevices.type, payload: { [expectedDevice1.id]: expectedDevice1, [expectedDevice2.id]: expectedDevice2 } },
1088
+ { type: getDevicesWithAuth.fulfilled.type }
1089
+ ];
1090
+ await store.dispatch(getDevicesWithAuth([defaultState.devices.byId.a1, defaultState.devices.byId.b1]));
1091
+ const storeActions = store.getActions();
1092
+ expect(storeActions.length).toEqual(expectedActions.length);
1093
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1094
+ });
1095
+ });
1096
+
1097
+ const deviceConfig = {
1098
+ configured: { aNumber: 42, something: 'else', test: true },
1099
+ reported: { aNumber: 42, something: 'else', test: true },
1100
+ updated_ts: defaultState.devices.byId.a1.updated_ts,
1101
+ reported_ts: '2019-01-01T09:25:01.000Z'
1102
+ };
1103
+
1104
+ describe('device config ', () => {
1105
+ it('should allow single device config retrieval', async () => {
1106
+ const store = mockStore({ ...defaultState });
1107
+ const expectedActions = [
1108
+ { type: getDeviceConfig.pending.type },
1109
+ { type: actions.receivedDevice.type, payload: { config: deviceConfig, id: defaultState.devices.byId.a1.id } },
1110
+ { type: getDeviceConfig.fulfilled.type }
1111
+ ];
1112
+ await store.dispatch(getDeviceConfig(defaultState.devices.byId.a1.id));
1113
+ const storeActions = store.getActions();
1114
+ expect(storeActions.length).toEqual(expectedActions.length);
1115
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1116
+ });
1117
+ it('should not have a problem with unknown devices on config retrieval', async () => {
1118
+ const store = mockStore({ ...defaultState });
1119
+ const expectedActions = [{ type: getDeviceConfig.pending.type }, { type: getDeviceConfig.fulfilled.type }];
1120
+ await store.dispatch(getDeviceConfig('testId'));
1121
+ const storeActions = store.getActions();
1122
+ expect(storeActions.length).toEqual(expectedActions.length);
1123
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1124
+ });
1125
+
1126
+ it('should allow single device config update', async () => {
1127
+ const store = mockStore({ ...defaultState });
1128
+ const expectedActions = [
1129
+ { type: setDeviceConfig.pending.type },
1130
+ { type: getDeviceConfig.pending.type },
1131
+ { type: actions.receivedDevice.type, payload: { config: deviceConfig, id: defaultState.devices.byId.a1.id } },
1132
+ { type: getDeviceConfig.fulfilled.type },
1133
+ { type: setDeviceConfig.fulfilled.type }
1134
+ ];
1135
+ await store.dispatch(setDeviceConfig({ deviceId: defaultState.devices.byId.a1.id, config: { something: 'asdl' } }));
1136
+ const storeActions = store.getActions();
1137
+ expect(storeActions.length).toEqual(expectedActions.length);
1138
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1139
+ });
1140
+ it('should allow single device config deployment', async () => {
1141
+ const store = mockStore({ ...defaultState });
1142
+ const expectedActions = [
1143
+ { type: applyDeviceConfig.pending.type },
1144
+ { type: actions.receivedDevice.type, payload: { ...defaultState.devices.byId.a1, config: { deployment_id: '' } } },
1145
+ { type: getSingleDeployment.type },
1146
+ { type: deploymentActions.receivedDeployment.type, payload: { ...defaultState.deployments.byId.d1, id: 'config1', created: '2019-01-01T09:25:01.000Z' } },
1147
+ { type: getSingleDeployment.type },
1148
+ { type: applyDeviceConfig.fulfilled.type }
1149
+ ];
1150
+ const result = store.dispatch(applyDeviceConfig({ deviceId: defaultState.devices.byId.a1.id, config: { something: 'asdl' } }));
1151
+ await act(async () => jest.runAllTicks());
1152
+ result.then(() => {
1153
+ const storeActions = store.getActions();
1154
+ expect(storeActions.length).toEqual(expectedActions.length);
1155
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1156
+ });
1157
+ });
1158
+ it('should allow setting device tags', async () => {
1159
+ const store = mockStore({ ...defaultState });
1160
+ const { attributes, id } = defaultState.devices.byId.a1;
1161
+ const expectedActions = [
1162
+ { type: setDeviceTags.pending.type },
1163
+ { type: getDeviceById.pending.type },
1164
+ { type: actions.receivedDevice.type, payload: { attributes, id } },
1165
+ { type: getDeviceById.fulfilled.type },
1166
+ { type: actions.receivedDevice.type, payload: { id, tags: { something: 'asdl' } } },
1167
+ { type: appActions.setSnackbar.type, payload: 'Device name changed' },
1168
+ { type: setDeviceTags.fulfilled.type }
1169
+ ];
1170
+ await store.dispatch(setDeviceTags({ deviceId: defaultState.devices.byId.a1.id, tags: { something: 'asdl' } }));
1171
+ const storeActions = store.getActions();
1172
+ expect(storeActions.length).toEqual(expectedActions.length);
1173
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1174
+ });
1175
+ });
1176
+
1177
+ describe('troubleshooting related actions', () => {
1178
+ it('should allow session info retrieval', async () => {
1179
+ const store = mockStore({ ...defaultState });
1180
+ const endDate = '2019-01-01T12:16:22.667Z';
1181
+ const sessionId = 'abd313a8-ee88-48ab-9c99-fbcd80048e6e';
1182
+ const result = await store
1183
+ .dispatch(getSessionDetails({ sessionId, deviceId: defaultState.devices.byId.a1.id, userId: defaultState.users.currentUser, endDate }))
1184
+ .unwrap();
1185
+
1186
+ expect(result).toMatchObject({ start: new Date(endDate), end: new Date(endDate) });
1187
+ });
1188
+
1189
+ it('should allow device file transfers', async () => {
1190
+ const store = mockStore({ ...defaultState });
1191
+ const link = await store.dispatch(getDeviceFileDownloadLink({ deviceId: 'aDeviceId', path: '/tmp/file' })).unwrap();
1192
+ expect(link).toBe('http://localhost/api/management/v1/deviceconnect/devices/aDeviceId/download?path=%2Ftmp%2Ffile');
1193
+ const expectedActions = [
1194
+ { type: getDeviceFileDownloadLink.pending.type },
1195
+ { type: getDeviceFileDownloadLink.fulfilled.type },
1196
+ { type: deviceFileUpload.pending.type },
1197
+ { type: appActions.setSnackbar.type, payload: 'Uploading file' },
1198
+ {
1199
+ type: appActions.initUpload.type,
1200
+ payload: { id: 'mock-uuid', upload: { cancelSource: mockAbortController, uploadProgress: 0 } }
1201
+ },
1202
+ { type: appActions.uploadProgress.type, payload: { id: 'mock-uuid', progress: 100 } },
1203
+ { type: appActions.setSnackbar.type, payload: 'Upload successful' },
1204
+ { type: appActions.cleanUpUpload.type, payload: 'mock-uuid' },
1205
+ { type: deviceFileUpload.fulfilled.type }
1206
+ ];
1207
+ await store.dispatch(deviceFileUpload({ deviceId: defaultState.devices.byId.a1.id, path: '/tmp/file', file: 'file' }));
1208
+ const storeActions = store.getActions();
1209
+ expect(storeActions.length).toEqual(expectedActions.length);
1210
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1211
+ });
1212
+ });
1213
+
1214
+ describe('device twin related actions', () => {
1215
+ it('should allow retrieving twin data from azure', async () => {
1216
+ const store = mockStore({ ...defaultState });
1217
+ const expectedActions = [
1218
+ { type: getDeviceTwin.pending.type },
1219
+ { type: actions.receivedDevice.type, payload: defaultState.devices.byId.a1 },
1220
+ { type: getDeviceTwin.fulfilled.type }
1221
+ ];
1222
+ await store.dispatch(getDeviceTwin({ deviceId: defaultState.devices.byId.a1.id, integration: EXTERNAL_PROVIDER['iot-hub'] }));
1223
+ const storeActions = store.getActions();
1224
+ expect(storeActions.length).toEqual(expectedActions.length);
1225
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1226
+ });
1227
+ it('should allow configuring twin data on azure', async () => {
1228
+ const store = mockStore({ ...defaultState });
1229
+ const expectedActions = [
1230
+ { type: setDeviceTwin.pending.type },
1231
+ { type: actions.receivedDevice.type, payload: defaultState.devices.byId.a1 },
1232
+ { type: setDeviceTwin.fulfilled.type }
1233
+ ];
1234
+ await store.dispatch(
1235
+ setDeviceTwin({
1236
+ deviceId: defaultState.devices.byId.a1.id,
1237
+ integration: EXTERNAL_PROVIDER['iot-hub'],
1238
+ settings: { something: 'asdl' }
1239
+ })
1240
+ );
1241
+ const storeActions = store.getActions();
1242
+ expect(storeActions.length).toEqual(expectedActions.length);
1243
+ expectedActions.map((action, index) => expect(storeActions[index]).toMatchObject(action));
1244
+ });
1245
+ });