@alwaysai/device-agent 1.3.0 → 1.3.1-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/environment-variables.d.ts +1 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +22 -20
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +37 -2
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/install.js +1 -1
- package/lib/application-control/install.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +5 -5
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +203 -178
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +30 -25
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +15 -0
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts +1 -3
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +1 -9
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/publisher.d.ts +1 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -1
- package/lib/cloud-connection/publisher.js +3 -0
- package/lib/cloud-connection/publisher.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +10 -21
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +154 -100
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +140 -72
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +26 -6
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +103 -22
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +179 -13
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +2 -2
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.js +435 -0
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
- package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
- package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
- package/lib/secure-tunneling/spawner-detached.js +107 -0
- package/lib/secure-tunneling/spawner-detached.js.map +1 -0
- package/lib/subcommands/app/analytics.d.ts +10 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -0
- package/lib/subcommands/app/analytics.js +79 -0
- package/lib/subcommands/app/analytics.js.map +1 -0
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +11 -16
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +3 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +0 -5
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +16 -56
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/status.d.ts +1 -0
- package/lib/subcommands/app/status.d.ts.map +1 -1
- package/lib/subcommands/app/status.js +14 -3
- package/lib/subcommands/app/status.js.map +1 -1
- package/lib/subcommands/app/version.d.ts +2 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +16 -3
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/cloud-mode-ready.d.ts +1 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -1
- package/lib/util/cloud-mode-ready.js +36 -1
- package/lib/util/cloud-mode-ready.js.map +1 -1
- package/lib/util/parsing.d.ts +2 -0
- package/lib/util/parsing.d.ts.map +1 -0
- package/lib/util/parsing.js +17 -0
- package/lib/util/parsing.js.map +1 -0
- package/package.json +4 -6
- package/readme.md +146 -92
- package/src/application-control/environment-variables.test.ts +43 -3
- package/src/application-control/environment-variables.ts +29 -19
- package/src/application-control/install.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +272 -247
- package/src/cloud-connection/live-updates-handler.test.ts +20 -0
- package/src/cloud-connection/live-updates-handler.ts +45 -52
- package/src/cloud-connection/messages.ts +1 -14
- package/src/cloud-connection/publisher.ts +4 -0
- package/src/cloud-connection/shadow-handler.test.ts +150 -73
- package/src/cloud-connection/shadow-handler.ts +247 -126
- package/src/cloud-connection/transaction-manager.test.ts +193 -18
- package/src/cloud-connection/transaction-manager.ts +174 -26
- package/src/device-control/device-control.ts +3 -3
- package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
- package/src/secure-tunneling/secure-tunneling.ts +606 -0
- package/src/secure-tunneling/spawner-detached.ts +123 -0
- package/src/subcommands/app/analytics.ts +102 -0
- package/src/subcommands/app/env-vars.ts +18 -16
- package/src/subcommands/app/index.ts +4 -3
- package/src/subcommands/app/models.ts +25 -57
- package/src/subcommands/app/status.ts +20 -3
- package/src/subcommands/app/version.ts +19 -4
- package/src/util/cloud-mode-ready.ts +36 -0
- package/src/util/parsing.ts +11 -0
- package/lib/cloud-connection/cmd-status.d.ts +0 -8
- package/lib/cloud-connection/cmd-status.d.ts.map +0 -1
- package/lib/cloud-connection/cmd-status.js +0 -62
- package/lib/cloud-connection/cmd-status.js.map +0 -1
- package/lib/cloud-connection/message-builder.d.ts +0 -7
- package/lib/cloud-connection/message-builder.d.ts.map +0 -1
- package/lib/cloud-connection/message-builder.js +0 -63
- package/lib/cloud-connection/message-builder.js.map +0 -1
- package/lib/secure-tunneling/index.d.ts +0 -5
- package/lib/secure-tunneling/index.d.ts.map +0 -1
- package/lib/secure-tunneling/index.js +0 -64
- package/lib/secure-tunneling/index.js.map +0 -1
- package/src/cloud-connection/cmd-status.ts +0 -71
- package/src/cloud-connection/message-builder.ts +0 -117
- package/src/secure-tunneling/index.ts +0 -74
|
@@ -3,6 +3,11 @@ import { Publisher } from './publisher';
|
|
|
3
3
|
|
|
4
4
|
global.setTimeout = jest.fn() as unknown as typeof setTimeout;
|
|
5
5
|
|
|
6
|
+
// https://github.com/facebook/react-native/issues/35701
|
|
7
|
+
Object.defineProperty(global, 'performance', {
|
|
8
|
+
writable: true
|
|
9
|
+
});
|
|
10
|
+
|
|
6
11
|
const testTrueToggles = {
|
|
7
12
|
deviceStats: true,
|
|
8
13
|
appState: true
|
|
@@ -66,4 +71,19 @@ describe('Test Live Updates Handler', () => {
|
|
|
66
71
|
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
|
|
67
72
|
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
68
73
|
});
|
|
74
|
+
|
|
75
|
+
test('timeout turns off live updates', async () => {
|
|
76
|
+
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
77
|
+
|
|
78
|
+
void liveUpdatesHandler.handleToggles(testTrueToggles, emptyTxId);
|
|
79
|
+
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(true);
|
|
80
|
+
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(true);
|
|
81
|
+
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
82
|
+
|
|
83
|
+
jest.runAllTimers();
|
|
84
|
+
|
|
85
|
+
expect(liveUpdatesHandler.getDeviceStatsLiveUpdates()).toBe(false);
|
|
86
|
+
expect(liveUpdatesHandler.getAppStateLiveUpdates()).toBe(false);
|
|
87
|
+
expect(liveUpdatesHandler.getAppLogsLiveUpdates()).toBe(false);
|
|
88
|
+
});
|
|
69
89
|
});
|
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AppLogsPayload,
|
|
3
|
-
ToClientMessagePayload,
|
|
4
3
|
keyMirrors,
|
|
5
4
|
LiveStateUpdatesTogglePayload,
|
|
6
|
-
ToClientMessage
|
|
5
|
+
ToClientMessage,
|
|
6
|
+
buildAppLogsMessage,
|
|
7
|
+
buildAppStateMessage,
|
|
8
|
+
buildDeviceStatsMessage,
|
|
9
|
+
StatusResponsePayload,
|
|
10
|
+
buildToClientStatusResponseMessage
|
|
7
11
|
} from '@alwaysai/device-agent-schemas';
|
|
8
12
|
import { getAppLogs } from '../application-control';
|
|
9
13
|
import { logger } from '../util/logger';
|
|
10
14
|
import sleep from '../util/sleep';
|
|
11
15
|
import { Publisher } from './publisher';
|
|
12
|
-
import {
|
|
13
|
-
getStatusResponsePayload,
|
|
14
|
-
getAppStatePayload,
|
|
15
|
-
getDeviceStatsPayload
|
|
16
|
-
} from './messages';
|
|
16
|
+
import { getAppStatePayload, getDeviceStatsPayload } from './messages';
|
|
17
17
|
import { ToClientMessageTypeValue } from '@alwaysai/device-agent-schemas';
|
|
18
18
|
import { ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS } from '../environment';
|
|
19
|
-
import {
|
|
20
|
-
buildAppLogsMessage,
|
|
21
|
-
buildAppStateMessage,
|
|
22
|
-
buildDeviceStatsMessage,
|
|
23
|
-
buildStatusResponseMessage
|
|
24
|
-
} from './message-builder';
|
|
25
19
|
|
|
26
20
|
const LIVE_UPDATES_TIMEOUT = ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS
|
|
27
21
|
? parseInt(ALWAYSAI_LIVE_UPDATES_TIMEOUT_MS)
|
|
@@ -48,8 +42,9 @@ export class LiveUpdatesHandler {
|
|
|
48
42
|
private appLogStreams = new Set<string>();
|
|
49
43
|
private transactionStatuses = new Set<string>();
|
|
50
44
|
|
|
51
|
-
private async startAppLogStream(projectId: string) {
|
|
45
|
+
private async startAppLogStream(projectId: string, txId: string) {
|
|
52
46
|
logger.info(`Starting log stream for ${projectId}`);
|
|
47
|
+
|
|
53
48
|
this.appLogStreams.add(projectId);
|
|
54
49
|
const readable = await getAppLogs({
|
|
55
50
|
projectId,
|
|
@@ -68,7 +63,7 @@ export class LiveUpdatesHandler {
|
|
|
68
63
|
projectId,
|
|
69
64
|
logChunk: logStr
|
|
70
65
|
};
|
|
71
|
-
const message =
|
|
66
|
+
const message = buildAppLogsMessage(this.clientId, payload, txId);
|
|
72
67
|
this.publisher.publishToClient(message, logger.silly);
|
|
73
68
|
});
|
|
74
69
|
|
|
@@ -117,7 +112,7 @@ export class LiveUpdatesHandler {
|
|
|
117
112
|
this.liveUpdatesAlive.device_stats = toggles.deviceStats;
|
|
118
113
|
}
|
|
119
114
|
if (toggles.appState !== undefined) {
|
|
120
|
-
this.liveUpdatesAlive.
|
|
115
|
+
this.liveUpdatesAlive.app_state = toggles.appState;
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
118
|
|
|
@@ -132,36 +127,25 @@ export class LiveUpdatesHandler {
|
|
|
132
127
|
}, LIVE_UPDATES_TIMEOUT);
|
|
133
128
|
}
|
|
134
129
|
|
|
135
|
-
private async startPublishingLiveUpdates
|
|
130
|
+
private async startPublishingLiveUpdates(
|
|
136
131
|
messageType: ToClientMessageTypeValue,
|
|
137
|
-
|
|
138
|
-
messageBuilderFunction: (
|
|
139
|
-
payload: ToClientMessagePayload,
|
|
140
|
-
txId: string
|
|
141
|
-
) => Promise<ToClientMessage>,
|
|
142
|
-
args: T,
|
|
132
|
+
getMessage: () => Promise<ToClientMessage>,
|
|
143
133
|
txId: string
|
|
144
134
|
) {
|
|
145
135
|
logger.info(`Turned on live updates for ${messageType}`);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!this.continuePublishing(messageType, txId)) {
|
|
150
|
-
logger.info(`Turned off live updates for ${messageType}`);
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
const payload: ToClientMessagePayload = await payloadBuilderFunction(
|
|
154
|
-
...args
|
|
155
|
-
);
|
|
156
|
-
const message = await messageBuilderFunction(payload, txId);
|
|
136
|
+
try {
|
|
137
|
+
while (this.continuePublishing(messageType, txId)) {
|
|
138
|
+
const message = await getMessage();
|
|
157
139
|
this.publisher.publishToClient(message, logger.silly);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
161
|
-
);
|
|
140
|
+
|
|
141
|
+
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
162
142
|
}
|
|
163
|
-
|
|
143
|
+
} catch (e) {
|
|
144
|
+
logger.error(
|
|
145
|
+
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
146
|
+
);
|
|
164
147
|
}
|
|
148
|
+
logger.info(`Turned off live updates for ${messageType}`);
|
|
165
149
|
}
|
|
166
150
|
|
|
167
151
|
/*=================================================================
|
|
@@ -196,9 +180,16 @@ export class LiveUpdatesHandler {
|
|
|
196
180
|
// Don't wait for this call to finish since it loops until disabled
|
|
197
181
|
void this.startPublishingLiveUpdates(
|
|
198
182
|
keyMirrors.toClientMessageType.status_response,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
183
|
+
async () => {
|
|
184
|
+
const payload: StatusResponsePayload = {
|
|
185
|
+
status: keyMirrors.statusResponse.in_progress
|
|
186
|
+
};
|
|
187
|
+
return buildToClientStatusResponseMessage(
|
|
188
|
+
this.clientId,
|
|
189
|
+
payload,
|
|
190
|
+
txId
|
|
191
|
+
);
|
|
192
|
+
},
|
|
202
193
|
txId
|
|
203
194
|
);
|
|
204
195
|
}
|
|
@@ -220,41 +211,43 @@ export class LiveUpdatesHandler {
|
|
|
220
211
|
const { deviceStats, appState, appLogs } = toggles;
|
|
221
212
|
this.restartLiveUpdatesTimeout();
|
|
222
213
|
|
|
223
|
-
const currentDeviceStats = this.getDeviceStatsLiveUpdates();
|
|
224
214
|
if (deviceStats !== undefined) {
|
|
215
|
+
const currentDeviceStats = this.getDeviceStatsLiveUpdates();
|
|
225
216
|
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
226
217
|
if (deviceStats && currentDeviceStats !== true) {
|
|
227
218
|
// Don't wait for this call to finish since it loops until disabled
|
|
228
219
|
void this.startPublishingLiveUpdates(
|
|
229
220
|
keyMirrors.toClientMessageType.device_stats,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
221
|
+
async () => {
|
|
222
|
+
const payload = await getDeviceStatsPayload();
|
|
223
|
+
return buildDeviceStatsMessage(this.clientId, payload, txId);
|
|
224
|
+
},
|
|
233
225
|
txId
|
|
234
226
|
);
|
|
235
227
|
}
|
|
236
228
|
}
|
|
237
229
|
|
|
238
|
-
const currentAppState = this.getAppStateLiveUpdates();
|
|
239
230
|
if (appState !== undefined) {
|
|
231
|
+
const currentAppState = this.getAppStateLiveUpdates();
|
|
240
232
|
this.liveUpdatesAlive.app_state = appState;
|
|
241
233
|
if (appState && currentAppState !== true) {
|
|
242
234
|
// Don't wait for this call to finish since it loops until disabled
|
|
243
235
|
void this.startPublishingLiveUpdates(
|
|
244
236
|
keyMirrors.toClientMessageType.app_state,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
237
|
+
async () => {
|
|
238
|
+
const payload = await getAppStatePayload();
|
|
239
|
+
return buildAppStateMessage(this.clientId, payload, txId);
|
|
240
|
+
},
|
|
248
241
|
txId
|
|
249
242
|
);
|
|
250
243
|
}
|
|
251
244
|
}
|
|
252
245
|
|
|
253
|
-
const currentAppLogs = this.getAppLogsLiveUpdates();
|
|
254
246
|
if (appLogs !== undefined) {
|
|
247
|
+
const currentAppLogs = this.getAppLogsLiveUpdates();
|
|
255
248
|
if (appLogs.toggle && currentAppLogs !== true) {
|
|
256
249
|
// Don't wait for this call to finish since it loops until disabled
|
|
257
|
-
void this.startAppLogStream(appLogs.projectId);
|
|
250
|
+
void this.startAppLogStream(appLogs.projectId, txId);
|
|
258
251
|
} else {
|
|
259
252
|
this.appLogStreams.delete(appLogs.projectId);
|
|
260
253
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AppState,
|
|
3
3
|
AppStatePayload,
|
|
4
|
-
DeviceStatsPayload
|
|
5
|
-
StatusResponsePayload
|
|
4
|
+
DeviceStatsPayload
|
|
6
5
|
} from '@alwaysai/device-agent-schemas';
|
|
7
|
-
import { StatusResponseValue } from '@alwaysai/device-agent-schemas/lib/constants';
|
|
8
6
|
import { getAppState } from '../application-control';
|
|
9
7
|
import {
|
|
10
8
|
getCpuDetails,
|
|
@@ -27,17 +25,6 @@ export async function getAppStatePayload(): Promise<AppStatePayload> {
|
|
|
27
25
|
return appStatePayload;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
export async function getStatusResponsePayload(
|
|
31
|
-
status: StatusResponseValue,
|
|
32
|
-
message: string
|
|
33
|
-
): Promise<StatusResponsePayload> {
|
|
34
|
-
const statusResponsePayload: StatusResponsePayload = {
|
|
35
|
-
status,
|
|
36
|
-
message
|
|
37
|
-
};
|
|
38
|
-
return statusResponsePayload;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
28
|
export async function getDeviceStatsPayload(): Promise<DeviceStatsPayload> {
|
|
42
29
|
const cpuDetails = await getCpuDetails();
|
|
43
30
|
const diskDetails = await getDiskDetails();
|
|
@@ -2,6 +2,9 @@ import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
|
2
2
|
import { readAppCfgFile } from '../application-control';
|
|
3
3
|
import { Publisher } from './publisher';
|
|
4
4
|
import { ShadowHandler } from './shadow-handler';
|
|
5
|
+
import { Logger } from 'winston';
|
|
6
|
+
import { logger } from '../util/logger';
|
|
7
|
+
import { getShadowTopic } from '@alwaysai/device-agent-schemas';
|
|
5
8
|
|
|
6
9
|
jest.mock('../application-control');
|
|
7
10
|
jest.mock('./publisher');
|
|
@@ -22,8 +25,8 @@ describe('Test Shadow Handler', () => {
|
|
|
22
25
|
test.skip('reject buffer payload', async () => {
|
|
23
26
|
//FIXME: Invalid input is silently ignored, need input validation
|
|
24
27
|
expect(async () => {
|
|
25
|
-
await shadowHandler.
|
|
26
|
-
topic:
|
|
28
|
+
await shadowHandler.handleProjectShadow({
|
|
29
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
27
30
|
payload: Buffer.from('test-payload'),
|
|
28
31
|
clientToken: ''
|
|
29
32
|
});
|
|
@@ -55,8 +58,8 @@ describe('Test Shadow Handler', () => {
|
|
|
55
58
|
}
|
|
56
59
|
};
|
|
57
60
|
|
|
58
|
-
const updates = await shadowHandler.
|
|
59
|
-
topic:
|
|
61
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
62
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
60
63
|
payload,
|
|
61
64
|
clientToken: clientId
|
|
62
65
|
});
|
|
@@ -65,11 +68,13 @@ describe('Test Shadow Handler', () => {
|
|
|
65
68
|
|
|
66
69
|
test('handle project shadow empty delta', async () => {
|
|
67
70
|
const payload = {
|
|
68
|
-
|
|
71
|
+
desired: {
|
|
72
|
+
[projectId1]: {}
|
|
73
|
+
}
|
|
69
74
|
};
|
|
70
75
|
|
|
71
|
-
const updates = await shadowHandler.
|
|
72
|
-
topic:
|
|
76
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
77
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
73
78
|
payload,
|
|
74
79
|
clientToken: ''
|
|
75
80
|
});
|
|
@@ -98,15 +103,17 @@ describe('Test Shadow Handler', () => {
|
|
|
98
103
|
};
|
|
99
104
|
|
|
100
105
|
const payload = {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
state: {
|
|
107
|
+
desired: {
|
|
108
|
+
[projectId1]: {
|
|
109
|
+
appConfig: JSON.stringify(appCfg1)
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
};
|
|
107
114
|
|
|
108
|
-
const updates = await shadowHandler.
|
|
109
|
-
topic:
|
|
115
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
116
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
110
117
|
payload,
|
|
111
118
|
clientToken: ''
|
|
112
119
|
});
|
|
@@ -145,13 +152,17 @@ describe('Test Shadow Handler', () => {
|
|
|
145
152
|
};
|
|
146
153
|
|
|
147
154
|
const payload = {
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
state: {
|
|
156
|
+
desired: {
|
|
157
|
+
[projectId1]: {
|
|
158
|
+
appConfig: JSON.stringify(appCfg1)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
150
161
|
}
|
|
151
162
|
};
|
|
152
163
|
|
|
153
|
-
const updates = await shadowHandler.
|
|
154
|
-
topic:
|
|
164
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
165
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
155
166
|
payload,
|
|
156
167
|
clientToken: ''
|
|
157
168
|
});
|
|
@@ -208,16 +219,20 @@ describe('Test Shadow Handler', () => {
|
|
|
208
219
|
}
|
|
209
220
|
};
|
|
210
221
|
const payload = {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
state: {
|
|
223
|
+
desired: {
|
|
224
|
+
[projectId1]: {
|
|
225
|
+
appConfig: JSON.stringify(appCfg1)
|
|
226
|
+
},
|
|
227
|
+
[projectId2]: {
|
|
228
|
+
appConfig: JSON.stringify(appCfg2)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
216
231
|
}
|
|
217
232
|
};
|
|
218
233
|
|
|
219
|
-
const updates = await shadowHandler.
|
|
220
|
-
topic:
|
|
234
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
235
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
221
236
|
payload,
|
|
222
237
|
clientToken: ''
|
|
223
238
|
});
|
|
@@ -265,13 +280,17 @@ describe('Test Shadow Handler', () => {
|
|
|
265
280
|
};
|
|
266
281
|
|
|
267
282
|
const payload = {
|
|
268
|
-
|
|
269
|
-
|
|
283
|
+
state: {
|
|
284
|
+
desired: {
|
|
285
|
+
[projectId1]: {
|
|
286
|
+
appConfig: JSON.stringify(appCfg1)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
270
289
|
}
|
|
271
290
|
};
|
|
272
291
|
|
|
273
|
-
const updates = await shadowHandler.
|
|
274
|
-
topic:
|
|
292
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
293
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
275
294
|
payload,
|
|
276
295
|
clientToken: ''
|
|
277
296
|
});
|
|
@@ -307,60 +326,110 @@ describe('Test Shadow Handler', () => {
|
|
|
307
326
|
};
|
|
308
327
|
|
|
309
328
|
const payload = {
|
|
310
|
-
|
|
311
|
-
|
|
329
|
+
state: {
|
|
330
|
+
desired: {
|
|
331
|
+
[projectId1]: {
|
|
332
|
+
appConfig: JSON.stringify(appCfg1)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
339
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
340
|
+
payload,
|
|
341
|
+
clientToken: ''
|
|
342
|
+
});
|
|
343
|
+
expect(updates.length).toBe(0);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('handles an unparsable object in a project shadow appCfg delta', async () => {
|
|
347
|
+
const ogAppCfg1: AppConfig = {
|
|
348
|
+
scripts: {
|
|
349
|
+
start: 'python app.py'
|
|
350
|
+
},
|
|
351
|
+
models: {}
|
|
352
|
+
};
|
|
353
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
354
|
+
|
|
355
|
+
// This appCfg is invalid on it's own (see values below)
|
|
356
|
+
const appCfg1 = {
|
|
357
|
+
scripts: {
|
|
358
|
+
start: 'python app.py'
|
|
359
|
+
},
|
|
360
|
+
models: {
|
|
361
|
+
'alwaysai/mobilenet_ssd': '3', // string instead of int
|
|
362
|
+
'alwaysai/yolo_v4': '5' // string instead of int
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const payload = {
|
|
367
|
+
state: {
|
|
368
|
+
desired: {
|
|
369
|
+
[projectId1]: {
|
|
370
|
+
appConfig: appCfg1 // This is missing JSON.stringify() making this an unparsable object.
|
|
371
|
+
}
|
|
372
|
+
}
|
|
312
373
|
}
|
|
313
374
|
};
|
|
314
375
|
|
|
315
|
-
const
|
|
316
|
-
|
|
376
|
+
const loggerSpy = jest
|
|
377
|
+
.spyOn(logger, 'error')
|
|
378
|
+
.mockReturnValue({} as unknown as Logger);
|
|
379
|
+
|
|
380
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
381
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
317
382
|
payload,
|
|
318
383
|
clientToken: ''
|
|
319
384
|
});
|
|
385
|
+
|
|
320
386
|
expect(updates.length).toBe(0);
|
|
387
|
+
expect(loggerSpy).toHaveBeenCalledWith(
|
|
388
|
+
expect.stringContaining('Error validating shadow update')
|
|
389
|
+
);
|
|
321
390
|
});
|
|
322
391
|
});
|
|
323
392
|
|
|
324
393
|
describe('handle project shadow env vars', () => {
|
|
325
|
-
test('handle
|
|
394
|
+
test('handle a response from the getAccepted from the cloud', async () => {
|
|
326
395
|
const envVars1 = {
|
|
327
396
|
VAR0: 'value0'
|
|
328
397
|
};
|
|
329
398
|
const payload = {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
399
|
+
state: {
|
|
400
|
+
reported: {
|
|
401
|
+
[projectId1]: {
|
|
402
|
+
envVars: { service: envVars1 }
|
|
403
|
+
}
|
|
333
404
|
}
|
|
334
405
|
}
|
|
335
406
|
};
|
|
336
407
|
|
|
337
|
-
const updates = await shadowHandler.
|
|
338
|
-
topic:
|
|
408
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
409
|
+
topic: getShadowTopic(clientId, 'projects', 'get/accepted'),
|
|
339
410
|
payload,
|
|
340
|
-
clientToken:
|
|
341
|
-
});
|
|
342
|
-
expect(updates.length).toBe(1);
|
|
343
|
-
expect(updates[0]).toEqual({
|
|
344
|
-
projectId: projectId1,
|
|
345
|
-
txId: expect.any(String),
|
|
346
|
-
envVarUpdate: {
|
|
347
|
-
envVars: envVars1
|
|
348
|
-
}
|
|
411
|
+
clientToken: clientId
|
|
349
412
|
});
|
|
413
|
+
expect(updates.length).toBe(0);
|
|
350
414
|
});
|
|
351
|
-
|
|
352
415
|
test('handle project shadow env vars update delta', async () => {
|
|
353
416
|
const envVars1 = {
|
|
354
417
|
VAR1: 'value1'
|
|
355
418
|
};
|
|
356
419
|
const payload = {
|
|
357
|
-
|
|
358
|
-
|
|
420
|
+
state: {
|
|
421
|
+
desired: {
|
|
422
|
+
[projectId1]: {
|
|
423
|
+
envVars: {
|
|
424
|
+
service: envVars1
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
359
428
|
}
|
|
360
429
|
};
|
|
361
430
|
|
|
362
|
-
const updates = await shadowHandler.
|
|
363
|
-
topic:
|
|
431
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
432
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
364
433
|
payload,
|
|
365
434
|
clientToken: ''
|
|
366
435
|
});
|
|
@@ -369,29 +438,36 @@ describe('Test Shadow Handler', () => {
|
|
|
369
438
|
projectId: projectId1,
|
|
370
439
|
txId: expect.any(String),
|
|
371
440
|
envVarUpdate: {
|
|
372
|
-
envVars: envVars1
|
|
441
|
+
envVars: { service: envVars1 }
|
|
373
442
|
}
|
|
374
443
|
});
|
|
375
444
|
});
|
|
376
445
|
|
|
377
446
|
test('handle project shadow env vars update delta for two projects', async () => {
|
|
378
|
-
const
|
|
447
|
+
const service1 = {
|
|
379
448
|
VAR1: 'value1'
|
|
380
449
|
};
|
|
381
|
-
const
|
|
382
|
-
VAR2:
|
|
450
|
+
const service2 = {
|
|
451
|
+
VAR2: 'value2'
|
|
383
452
|
};
|
|
453
|
+
|
|
384
454
|
const payload = {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
455
|
+
state: {
|
|
456
|
+
desired: {
|
|
457
|
+
[projectId1]: {
|
|
458
|
+
envVars: {
|
|
459
|
+
service: service1
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
[projectId2]: {
|
|
463
|
+
envVars: { service: service2 }
|
|
464
|
+
}
|
|
465
|
+
}
|
|
390
466
|
}
|
|
391
467
|
};
|
|
392
468
|
|
|
393
|
-
const updates = await shadowHandler.
|
|
394
|
-
topic:
|
|
469
|
+
const updates = await shadowHandler.handleProjectShadow({
|
|
470
|
+
topic: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
395
471
|
payload,
|
|
396
472
|
clientToken: ''
|
|
397
473
|
});
|
|
@@ -400,34 +476,35 @@ describe('Test Shadow Handler', () => {
|
|
|
400
476
|
projectId: projectId1,
|
|
401
477
|
txId: expect.any(String),
|
|
402
478
|
envVarUpdate: {
|
|
403
|
-
envVars:
|
|
479
|
+
envVars: { service: service1 }
|
|
404
480
|
}
|
|
405
481
|
});
|
|
406
482
|
expect(updates[1]).toEqual({
|
|
407
483
|
projectId: projectId2,
|
|
408
484
|
txId: expect.any(String),
|
|
409
485
|
envVarUpdate: {
|
|
410
|
-
envVars:
|
|
486
|
+
envVars: { service: service2 }
|
|
411
487
|
}
|
|
412
488
|
});
|
|
413
489
|
});
|
|
414
490
|
});
|
|
415
491
|
|
|
416
492
|
test('clear project shadow', async () => {
|
|
417
|
-
shadowHandler.
|
|
493
|
+
await shadowHandler.clearProjectShadow(projectId1);
|
|
494
|
+
// The order of reported and desired in the packet does not matter for the device agent, but the test is checking literal equality of expected output, and therefore the order matters here with reported being first.
|
|
418
495
|
const packet = {
|
|
496
|
+
clientToken: clientId,
|
|
419
497
|
state: {
|
|
420
|
-
|
|
498
|
+
reported: {
|
|
421
499
|
[projectId1]: null
|
|
422
500
|
},
|
|
423
|
-
|
|
501
|
+
desired: {
|
|
424
502
|
[projectId1]: null
|
|
425
503
|
}
|
|
426
|
-
}
|
|
427
|
-
clientToken: clientId
|
|
504
|
+
}
|
|
428
505
|
};
|
|
429
506
|
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
430
|
-
|
|
507
|
+
getShadowTopic(clientId, 'projects', 'update'),
|
|
431
508
|
JSON.stringify(packet)
|
|
432
509
|
);
|
|
433
510
|
});
|
|
@@ -453,15 +530,15 @@ describe('Test Shadow Handler', () => {
|
|
|
453
530
|
clientToken: clientId
|
|
454
531
|
};
|
|
455
532
|
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
456
|
-
|
|
533
|
+
getShadowTopic(clientId, 'projects', 'update'),
|
|
457
534
|
JSON.stringify(packet)
|
|
458
535
|
);
|
|
459
536
|
});
|
|
460
537
|
|
|
461
538
|
test('get shadow updates', async () => {
|
|
462
|
-
shadowHandler.
|
|
539
|
+
shadowHandler.getProjectShadowUpdates();
|
|
463
540
|
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
464
|
-
|
|
541
|
+
getShadowTopic(clientId, 'projects', 'get'),
|
|
465
542
|
JSON.stringify({
|
|
466
543
|
clientToken: clientId
|
|
467
544
|
})
|