edilkamin 1.4.0 → 1.5.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.
@@ -1,15 +1,19 @@
1
1
  import { strict as assert } from "assert";
2
- import sinon from "sinon";
3
2
  import axios from "axios";
4
- import { configure } from "../src/library";
3
+ import sinon from "sinon";
4
+
5
+ import { configure, createAuthService } from "../src/library";
6
+ import { API_URL } from "./constants";
5
7
 
6
8
  describe("library", () => {
7
9
  let axiosStub: sinon.SinonStub;
10
+ const expectedToken = "mockJwtToken";
8
11
 
9
12
  beforeEach(() => {
10
13
  axiosStub = sinon.stub(axios, "create").returns({
11
14
  get: sinon.stub(),
12
15
  put: sinon.stub(),
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
17
  } as any);
14
18
  });
15
19
 
@@ -17,60 +21,256 @@ describe("library", () => {
17
21
  sinon.restore();
18
22
  });
19
23
 
24
+ describe("signIn", () => {
25
+ it("should sign in and return the JWT token", async () => {
26
+ const expectedUsername = "testuser";
27
+ const expectedPassword = "testpassword";
28
+ const signIn = sinon.stub().resolves({ isSignedIn: true });
29
+ const signOut = sinon.stub();
30
+ const fetchAuthSession = sinon.stub().resolves({
31
+ tokens: {
32
+ accessToken: { toString: () => expectedToken },
33
+ },
34
+ });
35
+ const authStub = {
36
+ signIn,
37
+ signOut,
38
+ fetchAuthSession,
39
+ };
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ const authService = createAuthService(authStub as any);
42
+ const token = await authService.signIn(
43
+ expectedUsername,
44
+ expectedPassword
45
+ );
46
+ assert.deepEqual(authStub.signOut.args, [[]]);
47
+ assert.deepEqual(signIn.args, [
48
+ [{ username: expectedUsername, password: expectedPassword }],
49
+ ]);
50
+ assert.equal(token, expectedToken);
51
+ });
52
+
53
+ it("should throw an error if sign-in fails", async () => {
54
+ const expectedUsername = "testuser";
55
+ const expectedPassword = "testpassword";
56
+ const signIn = sinon.stub().resolves({ isSignedIn: false });
57
+ const signOut = sinon.stub();
58
+ const fetchAuthSession = sinon.stub().resolves({
59
+ tokens: {
60
+ accessToken: { toString: () => expectedToken },
61
+ },
62
+ });
63
+ const authStub = {
64
+ signIn,
65
+ signOut,
66
+ fetchAuthSession,
67
+ };
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ const authService = createAuthService(authStub as any);
70
+ await assert.rejects(
71
+ async () => authService.signIn(expectedUsername, expectedPassword),
72
+ {
73
+ name: "AssertionError",
74
+ message: "Sign-in failed",
75
+ }
76
+ );
77
+ });
78
+ });
79
+
20
80
  describe("configure", () => {
81
+ const expectedApi = [
82
+ "deviceInfo",
83
+ "setPower",
84
+ "setPowerOff",
85
+ "setPowerOn",
86
+ "getPower",
87
+ "getEnvironmentTemperature",
88
+ "getTargetTemperature",
89
+ "setTargetTemperature",
90
+ ];
21
91
  it("should create API methods with the correct baseURL", () => {
22
92
  const baseURL = "https://example.com/api";
23
93
  const api = configure(baseURL);
24
- assert.ok(axiosStub.calledOnce);
25
- assert.deepEqual(axiosStub.firstCall.args[0], { baseURL });
26
- assert.deepEqual(Object.keys(api), [
27
- "deviceInfo",
28
- "setPower",
29
- "setPowerOff",
30
- "setPowerOn",
94
+ assert.deepEqual(axiosStub.args, [
95
+ [
96
+ {
97
+ baseURL,
98
+ },
99
+ ],
31
100
  ]);
101
+ assert.deepEqual(Object.keys(api), expectedApi);
102
+ });
103
+ it("should create API methods with the default baseURL", () => {
104
+ const api = configure();
105
+ assert.deepEqual(axiosStub.args, [
106
+ [
107
+ {
108
+ baseURL: API_URL,
109
+ },
110
+ ],
111
+ ]);
112
+ assert.deepEqual(Object.keys(api), expectedApi);
32
113
  });
33
114
  });
34
115
 
35
116
  describe("API Methods", () => {
117
+ const mockDeviceInfo = {
118
+ status: {
119
+ commands: {
120
+ power: true,
121
+ },
122
+ temperatures: {
123
+ enviroment: 19,
124
+ },
125
+ },
126
+ nvm: {
127
+ user_parameters: {
128
+ enviroment_1_temperature: 22,
129
+ },
130
+ },
131
+ };
132
+
36
133
  it("should call axios for deviceInfo", async () => {
37
134
  const mockAxios = {
38
- get: sinon
39
- .stub()
40
- .resolves({ data: { id: "123", name: "Mock Device" } }),
135
+ get: sinon.stub().resolves({ data: mockDeviceInfo }),
41
136
  };
42
- axiosStub.returns(mockAxios as any);
137
+ axiosStub.returns(mockAxios);
43
138
  const api = configure("https://example.com/api");
44
- const result = await api.deviceInfo("mockToken", "mockMacAddress");
45
- assert.ok(mockAxios.get.calledOnce);
46
- assert.equal(
47
- mockAxios.get.firstCall.args[0],
48
- "device/mockMacAddress/info"
49
- );
50
- assert.deepEqual(mockAxios.get.firstCall.args[1], {
51
- headers: { Authorization: "Bearer mockToken" },
139
+ const result = await api.deviceInfo(expectedToken, "mockMacAddress");
140
+ assert.deepEqual(mockAxios.get.args, [
141
+ [
142
+ "device/mockMacAddress/info",
143
+ { headers: { Authorization: `Bearer ${expectedToken}` } },
144
+ ],
145
+ ]);
146
+ assert.deepEqual(result, mockDeviceInfo);
147
+ });
148
+
149
+ // Tests for setPowerOn and setPowerOff
150
+ [
151
+ {
152
+ method: "setPowerOn",
153
+ call: (api: ReturnType<typeof configure>) =>
154
+ api.setPowerOn("mockToken", "mockMacAddress"),
155
+ expectedValue: 1,
156
+ },
157
+ {
158
+ method: "setPowerOff",
159
+ call: (api: ReturnType<typeof configure>) =>
160
+ api.setPowerOff("mockToken", "mockMacAddress"),
161
+ expectedValue: 0,
162
+ },
163
+ ].forEach(({ method, call, expectedValue }) => {
164
+ it(`should call axios for ${method}`, async () => {
165
+ const mockAxios = {
166
+ put: sinon.stub().resolves({ status: 200 }),
167
+ };
168
+ axiosStub.returns(mockAxios);
169
+ const api = configure("https://example.com/api");
170
+
171
+ // Invoke the method using the mapped call function
172
+ const result = await call(api);
173
+ assert.deepEqual(mockAxios.put.args, [
174
+ [
175
+ "mqtt/command",
176
+ {
177
+ mac_address: "mockMacAddress",
178
+ name: "power",
179
+ value: expectedValue,
180
+ },
181
+ {
182
+ headers: { Authorization: "Bearer mockToken" },
183
+ },
184
+ ],
185
+ ]);
186
+ assert.equal(result.status, 200);
52
187
  });
53
- assert.deepEqual(result.data, { id: "123", name: "Mock Device" });
54
188
  });
55
189
 
56
- it("should call axios for setPowerOn", async () => {
57
- const mockAxios = {
58
- put: sinon.stub().resolves({ status: 200 }),
59
- };
60
- axiosStub.returns(mockAxios as any);
61
- const api = configure("https://example.com/api");
62
- const result = await api.setPowerOn("mockToken", "mockMacAddress");
63
- assert.ok(mockAxios.put.calledOnce);
64
- assert.equal(mockAxios.put.firstCall.args[0], "mqtt/command");
65
- assert.deepEqual(mockAxios.put.firstCall.args[1], {
66
- mac_address: "mockMacAddress",
67
- name: "power",
68
- value: 1,
190
+ const getterTests = [
191
+ {
192
+ method: "getPower",
193
+ call: (api: ReturnType<typeof configure>, token: string, mac: string) =>
194
+ api.getPower(token, mac),
195
+ expectedResult: true,
196
+ },
197
+ {
198
+ method: "getEnvironmentTemperature",
199
+ call: (api: ReturnType<typeof configure>, token: string, mac: string) =>
200
+ api.getEnvironmentTemperature(token, mac),
201
+ expectedResult: 19,
202
+ },
203
+ {
204
+ method: "getTargetTemperature",
205
+ call: (api: ReturnType<typeof configure>, token: string, mac: string) =>
206
+ api.getTargetTemperature(token, mac),
207
+ expectedResult: 22,
208
+ },
209
+ ];
210
+ getterTests.forEach(({ method, call, expectedResult }) => {
211
+ it(`should call axios and return the correct value for ${method}`, async () => {
212
+ const mockAxios = {
213
+ get: sinon.stub().resolves({ data: mockDeviceInfo }),
214
+ };
215
+ axiosStub.returns(mockAxios);
216
+ const api = configure("https://example.com/api");
217
+
218
+ const result = await call(api, expectedToken, "mockMacAddress");
219
+
220
+ assert.deepEqual(mockAxios.get.args, [
221
+ [
222
+ "device/mockMacAddress/info",
223
+ { headers: { Authorization: `Bearer ${expectedToken}` } },
224
+ ],
225
+ ]);
226
+ assert.equal(result, expectedResult);
69
227
  });
70
- assert.deepEqual(mockAxios.put.firstCall.args[2], {
71
- headers: { Authorization: "Bearer mockToken" },
228
+ });
229
+ // Setter tests
230
+ const setterTests = [
231
+ {
232
+ method: "setTargetTemperature",
233
+ call: (
234
+ api: ReturnType<typeof configure>,
235
+ token: string,
236
+ mac: string,
237
+ value: number
238
+ ) => api.setTargetTemperature(token, mac, value),
239
+ payload: {
240
+ name: "enviroment_1_temperature",
241
+ value: 20,
242
+ },
243
+ },
244
+ ];
245
+ setterTests.forEach(({ method, call, payload }) => {
246
+ it(`should call axios and send the correct payload for ${method}`, async () => {
247
+ const mockAxios = {
248
+ put: sinon.stub().resolves({ status: 200 }),
249
+ };
250
+ axiosStub.returns(mockAxios);
251
+ const api = configure("https://example.com/api");
252
+
253
+ const result = await call(
254
+ api,
255
+ expectedToken,
256
+ "mockMacAddress",
257
+ payload.value
258
+ );
259
+
260
+ assert.deepEqual(mockAxios.put.args, [
261
+ [
262
+ "mqtt/command",
263
+ {
264
+ mac_address: "mockMacAddress",
265
+ ...payload,
266
+ },
267
+ {
268
+ headers: { Authorization: `Bearer ${expectedToken}` },
269
+ },
270
+ ],
271
+ ]);
272
+ assert.equal(result.status, 200);
72
273
  });
73
- assert.equal(result.status, 200);
74
274
  });
75
275
  });
76
276
  });
package/src/library.ts CHANGED
@@ -2,43 +2,86 @@ import { strict as assert } from "assert";
2
2
  import { Amplify } from "aws-amplify";
3
3
  import * as amplifyAuth from "aws-amplify/auth";
4
4
  import axios, { AxiosInstance } from "axios";
5
- import { DeviceInfoType } from "./types";
5
+
6
6
  import { API_URL } from "./constants";
7
+ import { DeviceInfoType } from "./types";
7
8
 
8
9
  const amplifyconfiguration = {
9
10
  aws_project_region: "eu-central-1",
10
11
  aws_user_pools_id: "eu-central-1_BYmQ2VBlo",
11
12
  aws_user_pools_web_client_id: "7sc1qltkqobo3ddqsk4542dg2h",
12
13
  };
13
- Amplify.configure(amplifyconfiguration);
14
14
 
15
+ /**
16
+ * Generates headers with a JWT token for authenticated requests.
17
+ * @param {string} jwtToken - The JWT token for authorization.
18
+ * @returns {object} - The headers object with the Authorization field.
19
+ */
15
20
  const headers = (jwtToken: string) => ({ Authorization: `Bearer ${jwtToken}` });
16
21
 
17
22
  /**
18
- * Sign in to return the JWT token.
23
+ * Configures Amplify if not already configured.
24
+ * Ensures the configuration is only applied once.
25
+ */
26
+ const configureAmplify = () => {
27
+ const currentConfig = Amplify.getConfig();
28
+ if (Object.keys(currentConfig).length !== 0) return;
29
+ Amplify.configure(amplifyconfiguration);
30
+ };
31
+
32
+ /**
33
+ * Creates an authentication service with sign-in functionality.
34
+ * @param {typeof amplifyAuth} auth - The authentication module to use.
35
+ * @returns {object} - An object containing authentication-related methods.
19
36
  */
20
- const signIn = async (username: string, password: string): Promise<string> => {
21
- // in case the user is already signed in, refs:
22
- // https://github.com/aws-amplify/amplify-js/issues/13813
23
- await amplifyAuth.signOut();
24
- const { isSignedIn } = await amplifyAuth.signIn({
25
- username,
26
- password,
27
- });
28
- assert.ok(isSignedIn);
29
- const { tokens } = await amplifyAuth.fetchAuthSession();
30
- assert.ok(tokens);
31
- return tokens.accessToken.toString();
37
+ const createAuthService = (auth: typeof amplifyAuth) => {
38
+ /**
39
+ * Signs in a user with the provided credentials.
40
+ * @param {string} username - The username of the user.
41
+ * @param {string} password - The password of the user.
42
+ * @returns {Promise<string>} - The JWT token of the signed-in user.
43
+ * @throws {Error} - If sign-in fails or no tokens are retrieved.
44
+ */
45
+ const signIn = async (
46
+ username: string,
47
+ password: string
48
+ ): Promise<string> => {
49
+ configureAmplify();
50
+ await auth.signOut(); // Ensure the user is signed out first
51
+ const { isSignedIn } = await auth.signIn({ username, password });
52
+ assert.ok(isSignedIn, "Sign-in failed");
53
+ const { tokens } = await auth.fetchAuthSession();
54
+ assert.ok(tokens, "No tokens found");
55
+ return tokens.accessToken.toString();
56
+ };
57
+ return { signIn };
32
58
  };
33
59
 
60
+ // Create the default auth service using amplifyAuth
61
+ const { signIn } = createAuthService(amplifyAuth);
62
+
34
63
  const deviceInfo =
35
- (axiosInstance: AxiosInstance) => (jwtToken: string, macAddress: string) =>
36
- axiosInstance.get<DeviceInfoType>(`device/${macAddress}/info`, {
37
- headers: headers(jwtToken),
38
- });
64
+ (axiosInstance: AxiosInstance) =>
65
+ /**
66
+ * Retrieves information about a device by its MAC address.
67
+ *
68
+ * @param {string} jwtToken - The JWT token for authentication.
69
+ * @param {string} macAddress - The MAC address of the device.
70
+ * @returns {Promise<DeviceInfoType>} - A promise that resolves to the device info.
71
+ */
72
+ async (jwtToken: string, macAddress: string) => {
73
+ const response = await axiosInstance.get<DeviceInfoType>(
74
+ `device/${macAddress}/info`,
75
+ {
76
+ headers: headers(jwtToken),
77
+ }
78
+ );
79
+ return response.data;
80
+ };
39
81
 
40
82
  const mqttCommand =
41
83
  (axiosInstance: AxiosInstance) =>
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
85
  (jwtToken: string, macAddress: string, payload: any) =>
43
86
  axiosInstance.put(
44
87
  "mqtt/command",
@@ -48,28 +91,139 @@ const mqttCommand =
48
91
 
49
92
  const setPower =
50
93
  (axiosInstance: AxiosInstance) =>
94
+ /**
95
+ * Sends a command to set the power state of a device.
96
+ *
97
+ * @param {string} jwtToken - The JWT token for authentication.
98
+ * @param {string} macAddress - The MAC address of the device.
99
+ * @param {number} value - The desired power state (1 for ON, 0 for OFF).
100
+ * @returns {Promise<string>} - A promise that resolves to the command response.
101
+ */
51
102
  (jwtToken: string, macAddress: string, value: number) =>
52
103
  mqttCommand(axiosInstance)(jwtToken, macAddress, { name: "power", value });
53
104
 
54
105
  const setPowerOn =
55
- (axiosInstance: AxiosInstance) => (jwtToken: string, macAddress: string) =>
106
+ (axiosInstance: AxiosInstance) =>
107
+ /**
108
+ * Turns a device ON by setting its power state.
109
+ *
110
+ * @param {string} jwtToken - The JWT token for authentication.
111
+ * @param {string} macAddress - The MAC address of the device.
112
+ * @returns {Promise<string>} - A promise that resolves to the command response.
113
+ *
114
+ * @example
115
+ * const response = await api.setPowerOn(jwtToken, macAddress);
116
+ * console.log(response);
117
+ */
118
+ (jwtToken: string, macAddress: string) =>
56
119
  setPower(axiosInstance)(jwtToken, macAddress, 1);
120
+
57
121
  const setPowerOff =
58
- (axiosInstance: AxiosInstance) => (jwtToken: string, macAddress: string) =>
122
+ (axiosInstance: AxiosInstance) =>
123
+ /**
124
+ * Turns a device OFF by setting its power state.
125
+ *
126
+ * @param {string} jwtToken - The JWT token for authentication.
127
+ * @param {string} macAddress - The MAC address of the device.
128
+ * @returns {Promise<string>} - A promise that resolves to the command response.
129
+ *
130
+ * @example
131
+ * const response = await api.setPowerOff(jwtToken, macAddress);
132
+ * console.log(response);
133
+ */
134
+ (jwtToken: string, macAddress: string) =>
59
135
  setPower(axiosInstance)(jwtToken, macAddress, 0);
60
136
 
137
+ const getPower =
138
+ (axiosInstance: AxiosInstance) =>
139
+ /**
140
+ * Retrieves the power status of the device.
141
+ *
142
+ * @param {string} jwtToken - The JWT token for authentication.
143
+ * @param {string} macAddress - The MAC address of the device.
144
+ * @returns {Promise<boolean>} - A promise that resolves to the power status.
145
+ */
146
+ async (jwtToken: string, macAddress: string): Promise<boolean> => {
147
+ const info = await deviceInfo(axiosInstance)(jwtToken, macAddress);
148
+ return info.status.commands.power;
149
+ };
150
+
151
+ const getEnvironmentTemperature =
152
+ (axiosInstance: AxiosInstance) =>
153
+ /**
154
+ * Retrieves the environment temperature from the device's sensors.
155
+ *
156
+ * @param {string} jwtToken - The JWT token for authentication.
157
+ * @param {string} macAddress - The MAC address of the device.
158
+ * @returns {Promise<number>} - A promise that resolves to the temperature value.
159
+ */
160
+ async (jwtToken: string, macAddress: string): Promise<number> => {
161
+ const info = await deviceInfo(axiosInstance)(jwtToken, macAddress);
162
+ return info.status.temperatures.enviroment;
163
+ };
164
+
165
+ const getTargetTemperature =
166
+ (axiosInstance: AxiosInstance) =>
167
+ /**
168
+ * Retrieves the target temperature value set on the device.
169
+ *
170
+ * @param {string} jwtToken - The JWT token for authentication.
171
+ * @param {string} macAddress - The MAC address of the device.
172
+ * @returns {Promise<number>} - A promise that resolves to the target temperature (degree celsius).
173
+ */
174
+ async (jwtToken: string, macAddress: string): Promise<number> => {
175
+ const info = await deviceInfo(axiosInstance)(jwtToken, macAddress);
176
+ return info.nvm.user_parameters.enviroment_1_temperature;
177
+ };
178
+
179
+ const setTargetTemperature =
180
+ (axiosInstance: AxiosInstance) =>
181
+ /**
182
+ * Sends a command to set the target temperature (degree celsius) of a device.
183
+ *
184
+ * @param {string} jwtToken - The JWT token for authentication.
185
+ * @param {string} macAddress - The MAC address of the device.
186
+ * @param {number} temperature - The desired target temperature (degree celsius).
187
+ * @returns {Promise<string>} - A promise that resolves to the command response.
188
+ */
189
+ (jwtToken: string, macAddress: string, temperature: number) =>
190
+ mqttCommand(axiosInstance)(jwtToken, macAddress, {
191
+ name: "enviroment_1_temperature",
192
+ value: temperature,
193
+ });
194
+
195
+ /**
196
+ * Configures the library for API interactions.
197
+ * Initializes API methods with a specified base URL.
198
+ *
199
+ * @param {string} [baseURL=API_URL] - The base URL for the API.
200
+ * @returns {object} - An object containing methods for interacting with the API.
201
+ *
202
+ * @example
203
+ * const api = configure();
204
+ * const power = await api.getPower(jwtToken, macAddress);
205
+ */
61
206
  const configure = (baseURL: string = API_URL) => {
62
207
  const axiosInstance = axios.create({ baseURL });
63
208
  const deviceInfoInstance = deviceInfo(axiosInstance);
64
209
  const setPowerInstance = setPower(axiosInstance);
65
210
  const setPowerOffInstance = setPowerOff(axiosInstance);
66
211
  const setPowerOnInstance = setPowerOn(axiosInstance);
212
+ const getPowerInstance = getPower(axiosInstance);
213
+ const getEnvironmentTemperatureInstance =
214
+ getEnvironmentTemperature(axiosInstance);
215
+ const getTargetTemperatureInstance = getTargetTemperature(axiosInstance);
216
+ const setTargetTemperatureInstance = setTargetTemperature(axiosInstance);
67
217
  return {
68
218
  deviceInfo: deviceInfoInstance,
69
219
  setPower: setPowerInstance,
70
220
  setPowerOff: setPowerOffInstance,
71
221
  setPowerOn: setPowerOnInstance,
222
+ getPower: getPowerInstance,
223
+ getEnvironmentTemperature: getEnvironmentTemperatureInstance,
224
+ getTargetTemperature: getTargetTemperatureInstance,
225
+ setTargetTemperature: setTargetTemperatureInstance,
72
226
  };
73
227
  };
74
228
 
75
- export { signIn, configure };
229
+ export { configure, createAuthService, headers, signIn };