edilkamin 1.7.3 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/cli-tests.yml +6 -0
- package/README.md +36 -3
- package/dist/cjs/package.json +95 -0
- package/dist/cjs/src/bluetooth-utils.d.ts +13 -0
- package/dist/cjs/src/bluetooth-utils.js +28 -0
- package/dist/cjs/src/bluetooth-utils.test.js +35 -0
- package/dist/cjs/src/bluetooth.d.ts +40 -0
- package/dist/cjs/src/bluetooth.js +107 -0
- package/dist/cjs/src/browser-bundle.test.js +64 -0
- package/dist/cjs/src/buffer-utils.js +78 -0
- package/dist/cjs/src/buffer-utils.test.js +186 -0
- package/dist/cjs/src/cli.js +253 -0
- package/dist/cjs/src/configureAmplify.test.js +42 -0
- package/dist/cjs/src/constants.js +9 -0
- package/dist/{esm → cjs/src}/index.d.ts +2 -1
- package/dist/cjs/src/index.js +24 -0
- package/dist/cjs/src/library.js +324 -0
- package/dist/cjs/src/library.test.js +547 -0
- package/dist/cjs/src/serial-utils.js +50 -0
- package/dist/cjs/src/serial-utils.test.js +50 -0
- package/dist/cjs/src/token-storage.js +119 -0
- package/dist/{esm → cjs/src}/types.d.ts +14 -1
- package/dist/cjs/src/types.js +2 -0
- package/dist/esm/package.json +95 -0
- package/dist/esm/src/bluetooth-utils.d.ts +13 -0
- package/dist/esm/src/bluetooth-utils.js +25 -0
- package/dist/esm/src/bluetooth-utils.test.d.ts +1 -0
- package/dist/esm/src/bluetooth-utils.test.js +33 -0
- package/dist/esm/src/bluetooth.d.ts +40 -0
- package/dist/esm/src/bluetooth.js +100 -0
- package/dist/esm/src/browser-bundle.test.d.ts +1 -0
- package/dist/esm/{browser-bundle.test.js → src/browser-bundle.test.js} +1 -1
- package/dist/esm/src/buffer-utils.d.ts +25 -0
- package/dist/esm/src/buffer-utils.test.d.ts +1 -0
- package/dist/esm/src/cli.d.ts +3 -0
- package/dist/esm/src/configureAmplify.test.d.ts +1 -0
- package/dist/esm/src/constants.d.ts +4 -0
- package/dist/esm/src/index.d.ts +7 -0
- package/dist/esm/{index.js → src/index.js} +1 -0
- package/dist/esm/src/library.d.ts +55 -0
- package/dist/esm/src/library.test.d.ts +1 -0
- package/dist/esm/src/serial-utils.d.ts +33 -0
- package/dist/esm/src/serial-utils.test.d.ts +1 -0
- package/dist/esm/src/token-storage.d.ts +14 -0
- package/dist/esm/src/types.d.ts +86 -0
- package/dist/esm/src/types.js +1 -0
- package/package.json +22 -11
- package/src/bluetooth-utils.test.ts +46 -0
- package/src/bluetooth-utils.ts +29 -0
- package/src/bluetooth.ts +115 -0
- package/src/browser-bundle.test.ts +1 -1
- package/src/index.ts +2 -0
- package/src/types.ts +15 -0
- package/tsconfig.cjs.json +2 -2
- package/tsconfig.json +3 -3
- /package/dist/{esm/browser-bundle.test.d.ts → cjs/src/bluetooth-utils.test.d.ts} +0 -0
- /package/dist/{esm/buffer-utils.test.d.ts → cjs/src/browser-bundle.test.d.ts} +0 -0
- /package/dist/{esm → cjs/src}/buffer-utils.d.ts +0 -0
- /package/dist/{esm/configureAmplify.test.d.ts → cjs/src/buffer-utils.test.d.ts} +0 -0
- /package/dist/{esm → cjs/src}/cli.d.ts +0 -0
- /package/dist/{esm/library.test.d.ts → cjs/src/configureAmplify.test.d.ts} +0 -0
- /package/dist/{esm → cjs/src}/constants.d.ts +0 -0
- /package/dist/{esm → cjs/src}/library.d.ts +0 -0
- /package/dist/{esm/serial-utils.test.d.ts → cjs/src/library.test.d.ts} +0 -0
- /package/dist/{esm → cjs/src}/serial-utils.d.ts +0 -0
- /package/dist/{esm/types.js → cjs/src/serial-utils.test.d.ts} +0 -0
- /package/dist/{esm → cjs/src}/token-storage.d.ts +0 -0
- /package/dist/esm/{buffer-utils.js → src/buffer-utils.js} +0 -0
- /package/dist/esm/{buffer-utils.test.js → src/buffer-utils.test.js} +0 -0
- /package/dist/esm/{cli.js → src/cli.js} +0 -0
- /package/dist/esm/{configureAmplify.test.js → src/configureAmplify.test.js} +0 -0
- /package/dist/esm/{constants.js → src/constants.js} +0 -0
- /package/dist/esm/{library.js → src/library.js} +0 -0
- /package/dist/esm/{library.test.js → src/library.test.js} +0 -0
- /package/dist/esm/{serial-utils.js → src/serial-utils.js} +0 -0
- /package/dist/esm/{serial-utils.test.js → src/serial-utils.test.js} +0 -0
- /package/dist/esm/{token-storage.js → src/token-storage.js} +0 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const assert_1 = require("assert");
|
|
16
|
+
const pako_1 = __importDefault(require("pako"));
|
|
17
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
18
|
+
const library_1 = require("../src/library");
|
|
19
|
+
const constants_1 = require("./constants");
|
|
20
|
+
/**
|
|
21
|
+
* Helper to create a gzip-compressed Buffer object for testing.
|
|
22
|
+
*/
|
|
23
|
+
const createGzippedBuffer = (data) => {
|
|
24
|
+
const json = JSON.stringify(data);
|
|
25
|
+
const compressed = pako_1.default.gzip(json);
|
|
26
|
+
return {
|
|
27
|
+
type: "Buffer",
|
|
28
|
+
data: Array.from(compressed),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Helper to create a mock Response object for fetch.
|
|
33
|
+
*/
|
|
34
|
+
const mockResponse = (data, status = 200) => ({
|
|
35
|
+
ok: status >= 200 && status < 300,
|
|
36
|
+
status,
|
|
37
|
+
statusText: status >= 200 && status < 300 ? "OK" : "Error",
|
|
38
|
+
json: () => Promise.resolve(data),
|
|
39
|
+
});
|
|
40
|
+
describe("library", () => {
|
|
41
|
+
let fetchStub;
|
|
42
|
+
const expectedToken = "mockJwtToken";
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
fetchStub = sinon_1.default.stub(globalThis, "fetch");
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
sinon_1.default.restore();
|
|
48
|
+
});
|
|
49
|
+
describe("signIn", () => {
|
|
50
|
+
it("should sign in and return the ID token by default", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
51
|
+
const expectedUsername = "testuser";
|
|
52
|
+
const expectedPassword = "testpassword";
|
|
53
|
+
const signIn = sinon_1.default.stub().resolves({ isSignedIn: true });
|
|
54
|
+
const signOut = sinon_1.default.stub();
|
|
55
|
+
const fetchAuthSession = sinon_1.default.stub().resolves({
|
|
56
|
+
tokens: {
|
|
57
|
+
idToken: { toString: () => expectedToken },
|
|
58
|
+
accessToken: { toString: () => "accessToken" },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const authStub = {
|
|
62
|
+
signIn,
|
|
63
|
+
signOut,
|
|
64
|
+
fetchAuthSession,
|
|
65
|
+
};
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
const authService = (0, library_1.createAuthService)(authStub);
|
|
68
|
+
const token = yield authService.signIn(expectedUsername, expectedPassword);
|
|
69
|
+
assert_1.strict.deepEqual(authStub.signOut.args, [[]]);
|
|
70
|
+
assert_1.strict.deepEqual(signIn.args, [
|
|
71
|
+
[{ username: expectedUsername, password: expectedPassword }],
|
|
72
|
+
]);
|
|
73
|
+
assert_1.strict.equal(token, expectedToken);
|
|
74
|
+
}));
|
|
75
|
+
it("should sign in and return the access token in legacy mode", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
|
+
const expectedUsername = "testuser";
|
|
77
|
+
const expectedPassword = "testpassword";
|
|
78
|
+
const signIn = sinon_1.default.stub().resolves({ isSignedIn: true });
|
|
79
|
+
const signOut = sinon_1.default.stub();
|
|
80
|
+
const fetchAuthSession = sinon_1.default.stub().resolves({
|
|
81
|
+
tokens: {
|
|
82
|
+
accessToken: { toString: () => expectedToken },
|
|
83
|
+
idToken: { toString: () => "idToken" },
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
const authStub = {
|
|
87
|
+
signIn,
|
|
88
|
+
signOut,
|
|
89
|
+
fetchAuthSession,
|
|
90
|
+
};
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
const authService = (0, library_1.createAuthService)(authStub);
|
|
93
|
+
const token = yield authService.signIn(expectedUsername, expectedPassword, true);
|
|
94
|
+
assert_1.strict.equal(token, expectedToken);
|
|
95
|
+
}));
|
|
96
|
+
it("should throw an error if sign-in fails", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
+
const expectedUsername = "testuser";
|
|
98
|
+
const expectedPassword = "testpassword";
|
|
99
|
+
const signIn = sinon_1.default.stub().resolves({ isSignedIn: false });
|
|
100
|
+
const signOut = sinon_1.default.stub();
|
|
101
|
+
const fetchAuthSession = sinon_1.default.stub().resolves({
|
|
102
|
+
tokens: {
|
|
103
|
+
accessToken: { toString: () => expectedToken },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const authStub = {
|
|
107
|
+
signIn,
|
|
108
|
+
signOut,
|
|
109
|
+
fetchAuthSession,
|
|
110
|
+
};
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
112
|
+
const authService = (0, library_1.createAuthService)(authStub);
|
|
113
|
+
yield assert_1.strict.rejects(() => __awaiter(void 0, void 0, void 0, function* () { return authService.signIn(expectedUsername, expectedPassword); }), {
|
|
114
|
+
name: "AssertionError",
|
|
115
|
+
message: "Sign-in failed",
|
|
116
|
+
});
|
|
117
|
+
}));
|
|
118
|
+
});
|
|
119
|
+
describe("getSession", () => {
|
|
120
|
+
it("should return idToken by default", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
121
|
+
const mockAuth = {
|
|
122
|
+
signIn: sinon_1.default.stub().resolves({ isSignedIn: true }),
|
|
123
|
+
signOut: sinon_1.default.stub().resolves(),
|
|
124
|
+
fetchAuthSession: sinon_1.default.stub().resolves({
|
|
125
|
+
tokens: {
|
|
126
|
+
idToken: { toString: () => "mock-id-token" },
|
|
127
|
+
accessToken: { toString: () => "mock-access-token" },
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
};
|
|
131
|
+
const { getSession, signIn } = (0, library_1.createAuthService)(mockAuth);
|
|
132
|
+
yield signIn("user", "pass");
|
|
133
|
+
const token = yield getSession();
|
|
134
|
+
assert_1.strict.equal(token, "mock-id-token");
|
|
135
|
+
}));
|
|
136
|
+
it("should return accessToken when legacy=true", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
137
|
+
const mockAuth = {
|
|
138
|
+
signIn: sinon_1.default.stub().resolves({ isSignedIn: true }),
|
|
139
|
+
signOut: sinon_1.default.stub().resolves(),
|
|
140
|
+
fetchAuthSession: sinon_1.default.stub().resolves({
|
|
141
|
+
tokens: {
|
|
142
|
+
idToken: { toString: () => "mock-id-token" },
|
|
143
|
+
accessToken: { toString: () => "mock-access-token" },
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
};
|
|
147
|
+
const { getSession, signIn } = (0, library_1.createAuthService)(mockAuth);
|
|
148
|
+
yield signIn("user", "pass");
|
|
149
|
+
const token = yield getSession(false, true);
|
|
150
|
+
assert_1.strict.equal(token, "mock-access-token");
|
|
151
|
+
}));
|
|
152
|
+
it("should throw error when no session exists", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
153
|
+
const mockAuth = {
|
|
154
|
+
signIn: sinon_1.default.stub().resolves({ isSignedIn: true }),
|
|
155
|
+
signOut: sinon_1.default.stub().resolves(),
|
|
156
|
+
fetchAuthSession: sinon_1.default.stub().resolves({ tokens: null }),
|
|
157
|
+
};
|
|
158
|
+
const { getSession } = (0, library_1.createAuthService)(mockAuth);
|
|
159
|
+
yield assert_1.strict.rejects(() => __awaiter(void 0, void 0, void 0, function* () { return getSession(); }), {
|
|
160
|
+
name: "AssertionError",
|
|
161
|
+
message: "No session found - please sign in first",
|
|
162
|
+
});
|
|
163
|
+
}));
|
|
164
|
+
it("should pass forceRefresh to fetchAuthSession", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
165
|
+
const mockAuth = {
|
|
166
|
+
signIn: sinon_1.default.stub().resolves({ isSignedIn: true }),
|
|
167
|
+
signOut: sinon_1.default.stub().resolves(),
|
|
168
|
+
fetchAuthSession: sinon_1.default.stub().resolves({
|
|
169
|
+
tokens: {
|
|
170
|
+
idToken: { toString: () => "mock-id-token" },
|
|
171
|
+
accessToken: { toString: () => "mock-access-token" },
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
const { getSession, signIn } = (0, library_1.createAuthService)(mockAuth);
|
|
176
|
+
yield signIn("user", "pass");
|
|
177
|
+
yield getSession(true);
|
|
178
|
+
assert_1.strict.ok(mockAuth.fetchAuthSession.calledWith({ forceRefresh: true }));
|
|
179
|
+
}));
|
|
180
|
+
});
|
|
181
|
+
describe("configure", () => {
|
|
182
|
+
const expectedApi = [
|
|
183
|
+
"deviceInfo",
|
|
184
|
+
"registerDevice",
|
|
185
|
+
"editDevice",
|
|
186
|
+
"setPower",
|
|
187
|
+
"setPowerOff",
|
|
188
|
+
"setPowerOn",
|
|
189
|
+
"getPower",
|
|
190
|
+
"getEnvironmentTemperature",
|
|
191
|
+
"getTargetTemperature",
|
|
192
|
+
"setTargetTemperature",
|
|
193
|
+
];
|
|
194
|
+
it("should create API methods with the correct baseURL", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
195
|
+
const baseURL = "https://example.com/api/";
|
|
196
|
+
fetchStub.resolves(mockResponse({ test: "data" }));
|
|
197
|
+
const api = (0, library_1.configure)(baseURL);
|
|
198
|
+
assert_1.strict.deepEqual(Object.keys(api), expectedApi);
|
|
199
|
+
// Verify baseURL is used when making a request
|
|
200
|
+
yield api.deviceInfo(expectedToken, "mockMac");
|
|
201
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
202
|
+
assert_1.strict.ok(fetchStub.firstCall.args[0].startsWith(baseURL));
|
|
203
|
+
}));
|
|
204
|
+
it("should create API methods with the default baseURL", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
205
|
+
fetchStub.resolves(mockResponse({ test: "data" }));
|
|
206
|
+
const api = (0, library_1.configure)();
|
|
207
|
+
assert_1.strict.deepEqual(Object.keys(api), expectedApi);
|
|
208
|
+
// Verify default baseURL is used when making a request
|
|
209
|
+
yield api.deviceInfo(expectedToken, "mockMac");
|
|
210
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
211
|
+
assert_1.strict.ok(fetchStub.firstCall.args[0].startsWith(constants_1.API_URL));
|
|
212
|
+
}));
|
|
213
|
+
});
|
|
214
|
+
describe("API Methods", () => {
|
|
215
|
+
const mockDeviceInfo = {
|
|
216
|
+
status: {
|
|
217
|
+
commands: {
|
|
218
|
+
power: true,
|
|
219
|
+
},
|
|
220
|
+
temperatures: {
|
|
221
|
+
enviroment: 19,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
nvm: {
|
|
225
|
+
user_parameters: {
|
|
226
|
+
enviroment_1_temperature: 22,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
it("should call fetch for deviceInfo", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
231
|
+
fetchStub.resolves(mockResponse(mockDeviceInfo));
|
|
232
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
233
|
+
const result = yield api.deviceInfo(expectedToken, "mockMacAddress");
|
|
234
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
235
|
+
assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/device/mockMacAddress/info");
|
|
236
|
+
assert_1.strict.deepEqual(fetchStub.firstCall.args[1], {
|
|
237
|
+
method: "GET",
|
|
238
|
+
headers: { Authorization: `Bearer ${expectedToken}` },
|
|
239
|
+
});
|
|
240
|
+
assert_1.strict.deepEqual(result, mockDeviceInfo);
|
|
241
|
+
}));
|
|
242
|
+
// Tests for setPowerOn and setPowerOff
|
|
243
|
+
[
|
|
244
|
+
{
|
|
245
|
+
method: "setPowerOn",
|
|
246
|
+
call: (api) => api.setPowerOn("mockToken", "mockMacAddress"),
|
|
247
|
+
expectedValue: 1,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
method: "setPowerOff",
|
|
251
|
+
call: (api) => api.setPowerOff("mockToken", "mockMacAddress"),
|
|
252
|
+
expectedValue: 0,
|
|
253
|
+
},
|
|
254
|
+
].forEach(({ method, call, expectedValue }) => {
|
|
255
|
+
it(`should call fetch for ${method}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
256
|
+
fetchStub.resolves(mockResponse({ success: true }));
|
|
257
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
258
|
+
// Invoke the method using the mapped call function
|
|
259
|
+
yield call(api);
|
|
260
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
261
|
+
assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/mqtt/command");
|
|
262
|
+
assert_1.strict.deepEqual(fetchStub.firstCall.args[1], {
|
|
263
|
+
method: "PUT",
|
|
264
|
+
headers: {
|
|
265
|
+
"Content-Type": "application/json",
|
|
266
|
+
Authorization: "Bearer mockToken",
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify({
|
|
269
|
+
mac_address: "mockMacAddress",
|
|
270
|
+
name: "power",
|
|
271
|
+
value: expectedValue,
|
|
272
|
+
}),
|
|
273
|
+
});
|
|
274
|
+
}));
|
|
275
|
+
});
|
|
276
|
+
const getterTests = [
|
|
277
|
+
{
|
|
278
|
+
method: "getPower",
|
|
279
|
+
call: (api, token, mac) => api.getPower(token, mac),
|
|
280
|
+
expectedResult: true,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
method: "getEnvironmentTemperature",
|
|
284
|
+
call: (api, token, mac) => api.getEnvironmentTemperature(token, mac),
|
|
285
|
+
expectedResult: 19,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
method: "getTargetTemperature",
|
|
289
|
+
call: (api, token, mac) => api.getTargetTemperature(token, mac),
|
|
290
|
+
expectedResult: 22,
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
getterTests.forEach(({ method, call, expectedResult }) => {
|
|
294
|
+
it(`should call fetch and return the correct value for ${method}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
295
|
+
fetchStub.resolves(mockResponse(mockDeviceInfo));
|
|
296
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
297
|
+
const result = yield call(api, expectedToken, "mockMacAddress");
|
|
298
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
299
|
+
assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/device/mockMacAddress/info");
|
|
300
|
+
assert_1.strict.deepEqual(fetchStub.firstCall.args[1], {
|
|
301
|
+
method: "GET",
|
|
302
|
+
headers: { Authorization: `Bearer ${expectedToken}` },
|
|
303
|
+
});
|
|
304
|
+
assert_1.strict.equal(result, expectedResult);
|
|
305
|
+
}));
|
|
306
|
+
});
|
|
307
|
+
// Setter tests
|
|
308
|
+
const setterTests = [
|
|
309
|
+
{
|
|
310
|
+
method: "setTargetTemperature",
|
|
311
|
+
call: (api, token, mac, value) => api.setTargetTemperature(token, mac, value),
|
|
312
|
+
payload: {
|
|
313
|
+
name: "enviroment_1_temperature",
|
|
314
|
+
value: 20,
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
setterTests.forEach(({ method, call, payload }) => {
|
|
319
|
+
it(`should call fetch and send the correct payload for ${method}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
320
|
+
fetchStub.resolves(mockResponse({ success: true }));
|
|
321
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
322
|
+
yield call(api, expectedToken, "mockMacAddress", payload.value);
|
|
323
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
324
|
+
assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/mqtt/command");
|
|
325
|
+
assert_1.strict.deepEqual(fetchStub.firstCall.args[1], {
|
|
326
|
+
method: "PUT",
|
|
327
|
+
headers: {
|
|
328
|
+
"Content-Type": "application/json",
|
|
329
|
+
Authorization: `Bearer ${expectedToken}`,
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify(Object.assign({ mac_address: "mockMacAddress" }, payload)),
|
|
332
|
+
});
|
|
333
|
+
}));
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
describe("registerDevice", () => {
|
|
337
|
+
it("should call POST /device with correct payload", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
338
|
+
const mockResponseData = {
|
|
339
|
+
macAddress: "AABBCCDDEEFF",
|
|
340
|
+
deviceName: "Test Stove",
|
|
341
|
+
deviceRoom: "Living Room",
|
|
342
|
+
serialNumber: "EDK123",
|
|
343
|
+
};
|
|
344
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
345
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
346
|
+
const result = yield api.registerDevice(expectedToken, "AA:BB:CC:DD:EE:FF", "EDK123", "Test Stove", "Living Room");
|
|
347
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
348
|
+
assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/device");
|
|
349
|
+
assert_1.strict.deepEqual(fetchStub.firstCall.args[1], {
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers: {
|
|
352
|
+
"Content-Type": "application/json",
|
|
353
|
+
Authorization: `Bearer ${expectedToken}`,
|
|
354
|
+
},
|
|
355
|
+
body: JSON.stringify({
|
|
356
|
+
macAddress: "AABBCCDDEEFF",
|
|
357
|
+
deviceName: "Test Stove",
|
|
358
|
+
deviceRoom: "Living Room",
|
|
359
|
+
serialNumber: "EDK123",
|
|
360
|
+
}),
|
|
361
|
+
});
|
|
362
|
+
assert_1.strict.deepEqual(result, mockResponseData);
|
|
363
|
+
}));
|
|
364
|
+
it("should normalize MAC address by removing colons", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
365
|
+
fetchStub.resolves(mockResponse({}));
|
|
366
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
367
|
+
yield api.registerDevice(expectedToken, "AA:BB:CC:DD:EE:FF", "EDK123");
|
|
368
|
+
const body = JSON.parse(fetchStub.firstCall.args[1].body);
|
|
369
|
+
assert_1.strict.equal(body.macAddress, "AABBCCDDEEFF");
|
|
370
|
+
}));
|
|
371
|
+
it("should use empty strings as defaults for name and room", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
372
|
+
fetchStub.resolves(mockResponse({}));
|
|
373
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
374
|
+
yield api.registerDevice(expectedToken, "AABBCCDDEEFF", "EDK123");
|
|
375
|
+
const body = JSON.parse(fetchStub.firstCall.args[1].body);
|
|
376
|
+
assert_1.strict.equal(body.deviceName, "");
|
|
377
|
+
assert_1.strict.equal(body.deviceRoom, "");
|
|
378
|
+
}));
|
|
379
|
+
});
|
|
380
|
+
describe("editDevice", () => {
|
|
381
|
+
it("should call PUT /device/{mac} with correct payload", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
382
|
+
const mockResponseData = {
|
|
383
|
+
macAddress: "AABBCCDDEEFF",
|
|
384
|
+
deviceName: "Updated Name",
|
|
385
|
+
deviceRoom: "Basement",
|
|
386
|
+
serialNumber: "EDK123",
|
|
387
|
+
};
|
|
388
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
389
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
390
|
+
const result = yield api.editDevice(expectedToken, "AA:BB:CC:DD:EE:FF", "Updated Name", "Basement");
|
|
391
|
+
assert_1.strict.ok(fetchStub.calledOnce);
|
|
392
|
+
assert_1.strict.equal(fetchStub.firstCall.args[0], "https://example.com/api/device/AABBCCDDEEFF");
|
|
393
|
+
assert_1.strict.deepEqual(fetchStub.firstCall.args[1], {
|
|
394
|
+
method: "PUT",
|
|
395
|
+
headers: {
|
|
396
|
+
"Content-Type": "application/json",
|
|
397
|
+
Authorization: `Bearer ${expectedToken}`,
|
|
398
|
+
},
|
|
399
|
+
body: JSON.stringify({
|
|
400
|
+
deviceName: "Updated Name",
|
|
401
|
+
deviceRoom: "Basement",
|
|
402
|
+
}),
|
|
403
|
+
});
|
|
404
|
+
assert_1.strict.deepEqual(result, mockResponseData);
|
|
405
|
+
}));
|
|
406
|
+
it("should use empty strings as defaults for name and room", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
407
|
+
fetchStub.resolves(mockResponse({}));
|
|
408
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
409
|
+
yield api.editDevice(expectedToken, "AABBCCDDEEFF");
|
|
410
|
+
const body = JSON.parse(fetchStub.firstCall.args[1].body);
|
|
411
|
+
assert_1.strict.equal(body.deviceName, "");
|
|
412
|
+
assert_1.strict.equal(body.deviceRoom, "");
|
|
413
|
+
}));
|
|
414
|
+
});
|
|
415
|
+
describe("deviceInfo with compressed responses", () => {
|
|
416
|
+
it("should decompress Buffer-encoded status field", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
417
|
+
const statusData = {
|
|
418
|
+
commands: { power: true },
|
|
419
|
+
temperatures: { enviroment: 19, board: 25 },
|
|
420
|
+
};
|
|
421
|
+
const mockResponseData = {
|
|
422
|
+
status: createGzippedBuffer(statusData),
|
|
423
|
+
nvm: {
|
|
424
|
+
user_parameters: {
|
|
425
|
+
enviroment_1_temperature: 22,
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
430
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
431
|
+
const result = yield api.deviceInfo(expectedToken, "mockMacAddress");
|
|
432
|
+
assert_1.strict.deepEqual(result.status, statusData);
|
|
433
|
+
}));
|
|
434
|
+
it("should decompress Buffer-encoded nvm field", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
435
|
+
const nvmData = {
|
|
436
|
+
user_parameters: {
|
|
437
|
+
enviroment_1_temperature: 22,
|
|
438
|
+
enviroment_2_temperature: 0,
|
|
439
|
+
enviroment_3_temperature: 0,
|
|
440
|
+
is_auto: false,
|
|
441
|
+
is_sound_active: true,
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
const mockResponseData = {
|
|
445
|
+
status: {
|
|
446
|
+
commands: { power: true },
|
|
447
|
+
temperatures: { enviroment: 19 },
|
|
448
|
+
},
|
|
449
|
+
nvm: createGzippedBuffer(nvmData),
|
|
450
|
+
};
|
|
451
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
452
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
453
|
+
const result = yield api.deviceInfo(expectedToken, "mockMacAddress");
|
|
454
|
+
assert_1.strict.deepEqual(result.nvm, nvmData);
|
|
455
|
+
}));
|
|
456
|
+
it("should handle fully compressed response (status and nvm)", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
457
|
+
const statusData = {
|
|
458
|
+
commands: { power: false },
|
|
459
|
+
temperatures: { enviroment: 21, board: 30 },
|
|
460
|
+
};
|
|
461
|
+
const nvmData = {
|
|
462
|
+
user_parameters: {
|
|
463
|
+
enviroment_1_temperature: 20,
|
|
464
|
+
enviroment_2_temperature: 0,
|
|
465
|
+
enviroment_3_temperature: 0,
|
|
466
|
+
is_auto: true,
|
|
467
|
+
is_sound_active: false,
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
const mockResponseData = {
|
|
471
|
+
status: createGzippedBuffer(statusData),
|
|
472
|
+
nvm: createGzippedBuffer(nvmData),
|
|
473
|
+
};
|
|
474
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
475
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
476
|
+
const result = yield api.deviceInfo(expectedToken, "mockMacAddress");
|
|
477
|
+
assert_1.strict.deepEqual(result.status, statusData);
|
|
478
|
+
assert_1.strict.deepEqual(result.nvm, nvmData);
|
|
479
|
+
}));
|
|
480
|
+
it("should work with getPower on compressed response", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
481
|
+
const statusData = {
|
|
482
|
+
commands: { power: true },
|
|
483
|
+
temperatures: { enviroment: 19 },
|
|
484
|
+
};
|
|
485
|
+
const mockResponseData = {
|
|
486
|
+
status: createGzippedBuffer(statusData),
|
|
487
|
+
nvm: { user_parameters: { enviroment_1_temperature: 22 } },
|
|
488
|
+
};
|
|
489
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
490
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
491
|
+
const result = yield api.getPower(expectedToken, "mockMacAddress");
|
|
492
|
+
assert_1.strict.equal(result, true);
|
|
493
|
+
}));
|
|
494
|
+
it("should work with getEnvironmentTemperature on compressed response", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
495
|
+
const statusData = {
|
|
496
|
+
commands: { power: true },
|
|
497
|
+
temperatures: { enviroment: 19, board: 25 },
|
|
498
|
+
};
|
|
499
|
+
const mockResponseData = {
|
|
500
|
+
status: createGzippedBuffer(statusData),
|
|
501
|
+
nvm: { user_parameters: { enviroment_1_temperature: 22 } },
|
|
502
|
+
};
|
|
503
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
504
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
505
|
+
const result = yield api.getEnvironmentTemperature(expectedToken, "mockMacAddress");
|
|
506
|
+
assert_1.strict.equal(result, 19);
|
|
507
|
+
}));
|
|
508
|
+
it("should work with getTargetTemperature on compressed response", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
509
|
+
const nvmData = {
|
|
510
|
+
user_parameters: {
|
|
511
|
+
enviroment_1_temperature: 22,
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
const mockResponseData = {
|
|
515
|
+
status: { commands: { power: true }, temperatures: { enviroment: 19 } },
|
|
516
|
+
nvm: createGzippedBuffer(nvmData),
|
|
517
|
+
};
|
|
518
|
+
fetchStub.resolves(mockResponse(mockResponseData));
|
|
519
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
520
|
+
const result = yield api.getTargetTemperature(expectedToken, "mockMacAddress");
|
|
521
|
+
assert_1.strict.equal(result, 22);
|
|
522
|
+
}));
|
|
523
|
+
});
|
|
524
|
+
describe("Error Handling", () => {
|
|
525
|
+
const errorTests = [
|
|
526
|
+
{ status: 400, statusText: "Bad Request" },
|
|
527
|
+
{ status: 401, statusText: "Unauthorized" },
|
|
528
|
+
{ status: 404, statusText: "Not Found" },
|
|
529
|
+
{ status: 500, statusText: "Internal Server Error" },
|
|
530
|
+
];
|
|
531
|
+
errorTests.forEach(({ status, statusText }) => {
|
|
532
|
+
it(`should throw error when fetch returns ${status}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
533
|
+
const errorResponse = {
|
|
534
|
+
ok: false,
|
|
535
|
+
status,
|
|
536
|
+
statusText,
|
|
537
|
+
json: () => Promise.resolve({ error: statusText }),
|
|
538
|
+
};
|
|
539
|
+
fetchStub.resolves(errorResponse);
|
|
540
|
+
const api = (0, library_1.configure)("https://example.com/api/");
|
|
541
|
+
yield assert_1.strict.rejects(() => __awaiter(void 0, void 0, void 0, function* () { return api.deviceInfo(expectedToken, "mockMac"); }), {
|
|
542
|
+
message: `HTTP ${status}: ${statusText}`,
|
|
543
|
+
});
|
|
544
|
+
}));
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serialNumberToHex = exports.serialNumberFromHex = exports.serialNumberDisplay = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Converts a raw serial number string to hex-encoded format.
|
|
6
|
+
* This is useful when serial numbers contain non-printable characters.
|
|
7
|
+
*
|
|
8
|
+
* @param serial - The raw serial number string
|
|
9
|
+
* @returns Hex-encoded string representation
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* serialNumberToHex("EDK123") // returns "45444b313233"
|
|
13
|
+
*/
|
|
14
|
+
const serialNumberToHex = (serial) => {
|
|
15
|
+
return Buffer.from(serial, "utf-8").toString("hex");
|
|
16
|
+
};
|
|
17
|
+
exports.serialNumberToHex = serialNumberToHex;
|
|
18
|
+
/**
|
|
19
|
+
* Converts a hex-encoded serial number back to raw string format.
|
|
20
|
+
*
|
|
21
|
+
* @param hex - The hex-encoded serial number
|
|
22
|
+
* @returns Raw serial number string
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* serialNumberFromHex("45444b313233") // returns "EDK123"
|
|
26
|
+
*/
|
|
27
|
+
const serialNumberFromHex = (hex) => {
|
|
28
|
+
return Buffer.from(hex, "hex").toString("utf-8");
|
|
29
|
+
};
|
|
30
|
+
exports.serialNumberFromHex = serialNumberFromHex;
|
|
31
|
+
/**
|
|
32
|
+
* Produces a display-friendly version of a serial number by removing
|
|
33
|
+
* non-printable characters and collapsing whitespace.
|
|
34
|
+
*
|
|
35
|
+
* @param serial - The raw serial number string
|
|
36
|
+
* @returns Display-friendly serial number
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* serialNumberDisplay("EDK\x00123\x1F") // returns "EDK123"
|
|
40
|
+
*/
|
|
41
|
+
const serialNumberDisplay = (serial) => {
|
|
42
|
+
// Remove non-printable characters (ASCII 0-31, 127)
|
|
43
|
+
// Keep printable ASCII (32-126) and extended characters
|
|
44
|
+
return (serial
|
|
45
|
+
// eslint-disable-next-line no-control-regex
|
|
46
|
+
.replace(/[\x00-\x1F\x7F]/g, "")
|
|
47
|
+
.replace(/\s+/g, " ")
|
|
48
|
+
.trim());
|
|
49
|
+
};
|
|
50
|
+
exports.serialNumberDisplay = serialNumberDisplay;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const assert_1 = require("assert");
|
|
4
|
+
const serial_utils_1 = require("./serial-utils");
|
|
5
|
+
describe("serial-utils", () => {
|
|
6
|
+
describe("serialNumberToHex", () => {
|
|
7
|
+
it("should convert ASCII string to hex", () => {
|
|
8
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberToHex)("EDK123"), "45444b313233");
|
|
9
|
+
});
|
|
10
|
+
it("should handle empty string", () => {
|
|
11
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberToHex)(""), "");
|
|
12
|
+
});
|
|
13
|
+
it("should convert string with non-printable chars", () => {
|
|
14
|
+
const input = "EDK\x00123";
|
|
15
|
+
const hex = (0, serial_utils_1.serialNumberToHex)(input);
|
|
16
|
+
assert_1.strict.equal(hex, "45444b00313233");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("serialNumberFromHex", () => {
|
|
20
|
+
it("should convert hex back to ASCII string", () => {
|
|
21
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberFromHex)("45444b313233"), "EDK123");
|
|
22
|
+
});
|
|
23
|
+
it("should handle empty string", () => {
|
|
24
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberFromHex)(""), "");
|
|
25
|
+
});
|
|
26
|
+
it("should round-trip with toHex", () => {
|
|
27
|
+
const original = "EDK\x00123\x1F";
|
|
28
|
+
const hex = (0, serial_utils_1.serialNumberToHex)(original);
|
|
29
|
+
const restored = (0, serial_utils_1.serialNumberFromHex)(hex);
|
|
30
|
+
assert_1.strict.equal(restored, original);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe("serialNumberDisplay", () => {
|
|
34
|
+
it("should remove non-printable characters", () => {
|
|
35
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberDisplay)("EDK\x00123\x1F"), "EDK123");
|
|
36
|
+
});
|
|
37
|
+
it("should collapse whitespace", () => {
|
|
38
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberDisplay)("EDK 123"), "EDK 123");
|
|
39
|
+
});
|
|
40
|
+
it("should trim leading and trailing whitespace", () => {
|
|
41
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberDisplay)(" EDK123 "), "EDK123");
|
|
42
|
+
});
|
|
43
|
+
it("should handle empty string", () => {
|
|
44
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberDisplay)(""), "");
|
|
45
|
+
});
|
|
46
|
+
it("should preserve normal serial numbers", () => {
|
|
47
|
+
assert_1.strict.equal((0, serial_utils_1.serialNumberDisplay)("EDK12345678"), "EDK12345678");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|