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.
@@ -8,69 +8,238 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { strict as assert } from "assert";
11
- import sinon from "sinon";
12
11
  import axios from "axios";
13
- import { configure } from "../src/library";
12
+ import sinon from "sinon";
13
+ import { configure, createAuthService } from "../src/library";
14
+ import { API_URL } from "./constants";
14
15
  describe("library", () => {
15
16
  let axiosStub;
17
+ const expectedToken = "mockJwtToken";
16
18
  beforeEach(() => {
17
19
  axiosStub = sinon.stub(axios, "create").returns({
18
20
  get: sinon.stub(),
19
21
  put: sinon.stub(),
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
23
  });
21
24
  });
22
25
  afterEach(() => {
23
26
  sinon.restore();
24
27
  });
28
+ describe("signIn", () => {
29
+ it("should sign in and return the JWT token", () => __awaiter(void 0, void 0, void 0, function* () {
30
+ const expectedUsername = "testuser";
31
+ const expectedPassword = "testpassword";
32
+ const signIn = sinon.stub().resolves({ isSignedIn: true });
33
+ const signOut = sinon.stub();
34
+ const fetchAuthSession = sinon.stub().resolves({
35
+ tokens: {
36
+ accessToken: { toString: () => expectedToken },
37
+ },
38
+ });
39
+ const authStub = {
40
+ signIn,
41
+ signOut,
42
+ fetchAuthSession,
43
+ };
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ const authService = createAuthService(authStub);
46
+ const token = yield authService.signIn(expectedUsername, expectedPassword);
47
+ assert.deepEqual(authStub.signOut.args, [[]]);
48
+ assert.deepEqual(signIn.args, [
49
+ [{ username: expectedUsername, password: expectedPassword }],
50
+ ]);
51
+ assert.equal(token, expectedToken);
52
+ }));
53
+ it("should throw an error if sign-in fails", () => __awaiter(void 0, void 0, void 0, function* () {
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);
70
+ yield assert.rejects(() => __awaiter(void 0, void 0, void 0, function* () { return authService.signIn(expectedUsername, expectedPassword); }), {
71
+ name: "AssertionError",
72
+ message: "Sign-in failed",
73
+ });
74
+ }));
75
+ });
25
76
  describe("configure", () => {
77
+ const expectedApi = [
78
+ "deviceInfo",
79
+ "setPower",
80
+ "setPowerOff",
81
+ "setPowerOn",
82
+ "getPower",
83
+ "getEnvironmentTemperature",
84
+ "getTargetTemperature",
85
+ "setTargetTemperature",
86
+ ];
26
87
  it("should create API methods with the correct baseURL", () => {
27
88
  const baseURL = "https://example.com/api";
28
89
  const api = configure(baseURL);
29
- assert.ok(axiosStub.calledOnce);
30
- assert.deepEqual(axiosStub.firstCall.args[0], { baseURL });
31
- assert.deepEqual(Object.keys(api), [
32
- "deviceInfo",
33
- "setPower",
34
- "setPowerOff",
35
- "setPowerOn",
90
+ assert.deepEqual(axiosStub.args, [
91
+ [
92
+ {
93
+ baseURL,
94
+ },
95
+ ],
96
+ ]);
97
+ assert.deepEqual(Object.keys(api), expectedApi);
98
+ });
99
+ it("should create API methods with the default baseURL", () => {
100
+ const api = configure();
101
+ assert.deepEqual(axiosStub.args, [
102
+ [
103
+ {
104
+ baseURL: API_URL,
105
+ },
106
+ ],
36
107
  ]);
108
+ assert.deepEqual(Object.keys(api), expectedApi);
37
109
  });
38
110
  });
39
111
  describe("API Methods", () => {
112
+ const mockDeviceInfo = {
113
+ status: {
114
+ commands: {
115
+ power: true,
116
+ },
117
+ temperatures: {
118
+ enviroment: 19,
119
+ },
120
+ },
121
+ nvm: {
122
+ user_parameters: {
123
+ enviroment_1_temperature: 22,
124
+ },
125
+ },
126
+ };
40
127
  it("should call axios for deviceInfo", () => __awaiter(void 0, void 0, void 0, function* () {
41
128
  const mockAxios = {
42
- get: sinon
43
- .stub()
44
- .resolves({ data: { id: "123", name: "Mock Device" } }),
129
+ get: sinon.stub().resolves({ data: mockDeviceInfo }),
45
130
  };
46
131
  axiosStub.returns(mockAxios);
47
132
  const api = configure("https://example.com/api");
48
- const result = yield api.deviceInfo("mockToken", "mockMacAddress");
49
- assert.ok(mockAxios.get.calledOnce);
50
- assert.equal(mockAxios.get.firstCall.args[0], "device/mockMacAddress/info");
51
- assert.deepEqual(mockAxios.get.firstCall.args[1], {
52
- headers: { Authorization: "Bearer mockToken" },
53
- });
54
- assert.deepEqual(result.data, { id: "123", name: "Mock Device" });
55
- }));
56
- it("should call axios for setPowerOn", () => __awaiter(void 0, void 0, void 0, function* () {
57
- const mockAxios = {
58
- put: sinon.stub().resolves({ status: 200 }),
59
- };
60
- axiosStub.returns(mockAxios);
61
- const api = configure("https://example.com/api");
62
- const result = yield 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,
69
- });
70
- assert.deepEqual(mockAxios.put.firstCall.args[2], {
71
- headers: { Authorization: "Bearer mockToken" },
72
- });
73
- assert.equal(result.status, 200);
133
+ const result = yield api.deviceInfo(expectedToken, "mockMacAddress");
134
+ assert.deepEqual(mockAxios.get.args, [
135
+ [
136
+ "device/mockMacAddress/info",
137
+ { headers: { Authorization: `Bearer ${expectedToken}` } },
138
+ ],
139
+ ]);
140
+ assert.deepEqual(result, mockDeviceInfo);
74
141
  }));
142
+ // Tests for setPowerOn and setPowerOff
143
+ [
144
+ {
145
+ method: "setPowerOn",
146
+ call: (api) => api.setPowerOn("mockToken", "mockMacAddress"),
147
+ expectedValue: 1,
148
+ },
149
+ {
150
+ method: "setPowerOff",
151
+ call: (api) => api.setPowerOff("mockToken", "mockMacAddress"),
152
+ expectedValue: 0,
153
+ },
154
+ ].forEach(({ method, call, expectedValue }) => {
155
+ it(`should call axios for ${method}`, () => __awaiter(void 0, void 0, void 0, function* () {
156
+ const mockAxios = {
157
+ put: sinon.stub().resolves({ status: 200 }),
158
+ };
159
+ axiosStub.returns(mockAxios);
160
+ const api = configure("https://example.com/api");
161
+ // Invoke the method using the mapped call function
162
+ const result = yield call(api);
163
+ assert.deepEqual(mockAxios.put.args, [
164
+ [
165
+ "mqtt/command",
166
+ {
167
+ mac_address: "mockMacAddress",
168
+ name: "power",
169
+ value: expectedValue,
170
+ },
171
+ {
172
+ headers: { Authorization: "Bearer mockToken" },
173
+ },
174
+ ],
175
+ ]);
176
+ assert.equal(result.status, 200);
177
+ }));
178
+ });
179
+ const getterTests = [
180
+ {
181
+ method: "getPower",
182
+ call: (api, token, mac) => api.getPower(token, mac),
183
+ expectedResult: true,
184
+ },
185
+ {
186
+ method: "getEnvironmentTemperature",
187
+ call: (api, token, mac) => api.getEnvironmentTemperature(token, mac),
188
+ expectedResult: 19,
189
+ },
190
+ {
191
+ method: "getTargetTemperature",
192
+ call: (api, token, mac) => api.getTargetTemperature(token, mac),
193
+ expectedResult: 22,
194
+ },
195
+ ];
196
+ getterTests.forEach(({ method, call, expectedResult }) => {
197
+ it(`should call axios and return the correct value for ${method}`, () => __awaiter(void 0, void 0, void 0, function* () {
198
+ const mockAxios = {
199
+ get: sinon.stub().resolves({ data: mockDeviceInfo }),
200
+ };
201
+ axiosStub.returns(mockAxios);
202
+ const api = configure("https://example.com/api");
203
+ const result = yield call(api, expectedToken, "mockMacAddress");
204
+ assert.deepEqual(mockAxios.get.args, [
205
+ [
206
+ "device/mockMacAddress/info",
207
+ { headers: { Authorization: `Bearer ${expectedToken}` } },
208
+ ],
209
+ ]);
210
+ assert.equal(result, expectedResult);
211
+ }));
212
+ });
213
+ // Setter tests
214
+ const setterTests = [
215
+ {
216
+ method: "setTargetTemperature",
217
+ call: (api, token, mac, value) => api.setTargetTemperature(token, mac, value),
218
+ payload: {
219
+ name: "enviroment_1_temperature",
220
+ value: 20,
221
+ },
222
+ },
223
+ ];
224
+ setterTests.forEach(({ method, call, payload }) => {
225
+ it(`should call axios and send the correct payload for ${method}`, () => __awaiter(void 0, void 0, void 0, function* () {
226
+ const mockAxios = {
227
+ put: sinon.stub().resolves({ status: 200 }),
228
+ };
229
+ axiosStub.returns(mockAxios);
230
+ const api = configure("https://example.com/api");
231
+ const result = yield call(api, expectedToken, "mockMacAddress", payload.value);
232
+ assert.deepEqual(mockAxios.put.args, [
233
+ [
234
+ "mqtt/command",
235
+ Object.assign({ mac_address: "mockMacAddress" }, payload),
236
+ {
237
+ headers: { Authorization: `Bearer ${expectedToken}` },
238
+ },
239
+ ],
240
+ ]);
241
+ assert.equal(result.status, 200);
242
+ }));
243
+ });
75
244
  });
76
245
  });
@@ -0,0 +1,35 @@
1
+ import typescriptEslint from "@typescript-eslint/eslint-plugin";
2
+ import simpleImportSort from "eslint-plugin-simple-import-sort";
3
+ import globals from "globals";
4
+ import tsParser from "@typescript-eslint/parser";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import js from "@eslint/js";
8
+ import { FlatCompat } from "@eslint/eslintrc";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ const compat = new FlatCompat({
14
+ baseDirectory: __dirname,
15
+ recommendedConfig: js.configs.recommended,
16
+ allConfig: js.configs.all,
17
+ });
18
+
19
+ export default [
20
+ ...compat.extends(
21
+ "eslint:recommended",
22
+ "plugin:@typescript-eslint/recommended"
23
+ ),
24
+ {
25
+ plugins: {
26
+ "@typescript-eslint": typescriptEslint,
27
+ "simple-import-sort": simpleImportSort,
28
+ },
29
+ rules: {
30
+ // Sorting imports and exports
31
+ "simple-import-sort/imports": "error",
32
+ "simple-import-sort/exports": "error",
33
+ },
34
+ },
35
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edilkamin",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -8,10 +8,14 @@
8
8
  "scripts": {
9
9
  "cli": "ts-node src/cli.ts",
10
10
  "cli:debug": "node --inspect --require ts-node/register/transpile-only src/cli.ts",
11
- "test": "mocha --require ts-node/register src/*.test.ts",
12
- "test:debug": "mocha --require ts-node/register/transpile-only --inspect src/*.test.ts",
13
- "lint": "prettier --check src docs .github *.md *.json",
14
- "format": "prettier --write src docs .github *.md *.json",
11
+ "test": "nyc mocha --require ts-node/register src/*.test.ts",
12
+ "test:debug": "nyc mocha --require ts-node/register/transpile-only --inspect src/*.test.ts",
13
+ "lint:prettier": "prettier --check src docs .github *.json *.md *.mjs",
14
+ "format:prettier": "prettier --write src docs .github *.json *.md *.mjs",
15
+ "lint:eslint": "eslint src",
16
+ "format:eslint": "eslint --fix src",
17
+ "lint": "yarn lint:prettier && yarn lint:eslint",
18
+ "format": "yarn format:prettier && yarn format:eslint",
15
19
  "build:cjs": "tsc -p tsconfig.cjs.json",
16
20
  "build:esm": "tsc -p tsconfig.esm.json",
17
21
  "build": "npm run build:cjs && npm run build:esm"
@@ -29,15 +33,31 @@
29
33
  "bin": {
30
34
  "edilkamin": "dist/cjs/cli.js"
31
35
  },
36
+ "nyc": {
37
+ "reporter": [
38
+ "html",
39
+ "lcov",
40
+ "text"
41
+ ]
42
+ },
32
43
  "dependencies": {
33
44
  "aws-amplify": "^6.10.0",
34
45
  "axios": "^0.26.0"
35
46
  },
36
47
  "devDependencies": {
37
48
  "@aws-amplify/cli": "^7.6.21",
49
+ "@eslint/eslintrc": "^3.2.0",
50
+ "@eslint/js": "^9.16.0",
38
51
  "@types/mocha": "^10.0.10",
39
52
  "@types/sinon": "^17.0.3",
53
+ "@typescript-eslint/eslint-plugin": "^8.17.0",
54
+ "@typescript-eslint/parser": "^8.17.0",
55
+ "eslint": "^9.16.0",
56
+ "eslint-config-prettier": "^9.1.0",
57
+ "eslint-plugin-prettier": "^5.2.1",
58
+ "eslint-plugin-simple-import-sort": "^12.1.1",
40
59
  "mocha": "^10.8.2",
60
+ "nyc": "^17.1.0",
41
61
  "prettier": "^2.5.1",
42
62
  "sinon": "^19.0.2",
43
63
  "ts-node": "^10.9.1",
package/src/cli.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { signIn, configure } from "./library";
3
2
  import { Command } from "commander";
4
3
  import readline from "readline";
4
+
5
5
  import { version } from "../package.json";
6
+ import { configure, signIn } from "./library";
6
7
 
7
8
  const promptPassword = (): Promise<string> => {
8
9
  const rl = readline.createInterface({
@@ -31,11 +32,78 @@ const promptPassword = (): Promise<string> => {
31
32
  * @param command The command to which options should be added.
32
33
  * @returns The command with options added.
33
34
  */
34
- const addCommonOptions = (command: Command): Command =>
35
+ const addAuthOptions = (command: Command): Command =>
35
36
  command
36
37
  .requiredOption("-u, --username <username>", "Username")
37
38
  .option("-p, --password <password>", "Password");
38
39
 
40
+ /**
41
+ * Adds MAC address option to a command.
42
+ * @param command The command to which the MAC address option should be added.
43
+ * @returns The command with the MAC address option added.
44
+ */
45
+ const addMacOption = (command: Command): Command =>
46
+ command.requiredOption("-m, --mac <macAddress>", "MAC address of the device");
47
+
48
+ /**
49
+ * Handles common authentication and API initialization logic.
50
+ * @param options The options passed from the CLI command.
51
+ * @returns An object containing the normalized MAC, JWT token, and configured API instance.
52
+ */
53
+ const initializeCommand = async (options: {
54
+ username: string;
55
+ password?: string;
56
+ mac: string;
57
+ }): Promise<{
58
+ normalizedMac: string;
59
+ jwtToken: string;
60
+ api: ReturnType<typeof configure>;
61
+ }> => {
62
+ const { username, password, mac } = options;
63
+ const normalizedMac = mac.replace(/:/g, "");
64
+ const pwd = password || (await promptPassword());
65
+ const jwtToken = await signIn(username, pwd);
66
+ const api = configure();
67
+ return { normalizedMac, jwtToken, api };
68
+ };
69
+
70
+ /**
71
+ * Executes a getter command by handling common steps (authentication, API initialization).
72
+ * @param options The options passed from the CLI command.
73
+ * @param getter A function to call on the configured API object.
74
+ */
75
+ const executeGetter = async (
76
+ options: { username: string; password?: string; mac: string },
77
+ getter: (
78
+ api: ReturnType<typeof configure>,
79
+ jwtToken: string,
80
+ mac: string
81
+ ) => Promise<unknown>
82
+ ): Promise<void> => {
83
+ const { normalizedMac, jwtToken, api } = await initializeCommand(options);
84
+ const result = await getter(api, jwtToken, normalizedMac);
85
+ console.log(result);
86
+ };
87
+
88
+ /**
89
+ * Executes a setter command by handling common steps (authentication, API initialization).
90
+ * @param options The options passed from the CLI command.
91
+ * @param setter A function to call on the configured API object.
92
+ */
93
+ const executeSetter = async (
94
+ options: { username: string; password?: string; mac: string; value: number },
95
+ setter: (
96
+ api: ReturnType<typeof configure>,
97
+ jwtToken: string,
98
+ mac: string,
99
+ value: number
100
+ ) => Promise<unknown>
101
+ ): Promise<void> => {
102
+ const { normalizedMac, jwtToken, api } = await initializeCommand(options);
103
+ const result = await setter(api, jwtToken, normalizedMac, options.value);
104
+ console.log(result);
105
+ };
106
+
39
107
  const createProgram = (): Command => {
40
108
  const program = new Command();
41
109
  program
@@ -43,7 +111,7 @@ const createProgram = (): Command => {
43
111
  .description("CLI tool for interacting with the Edilkamin API")
44
112
  .version(version);
45
113
  // Command: signIn
46
- addCommonOptions(
114
+ addAuthOptions(
47
115
  program.command("signIn").description("Sign in and retrieve a JWT token")
48
116
  ).action(async (options) => {
49
117
  const { username, password } = options;
@@ -51,20 +119,79 @@ const createProgram = (): Command => {
51
119
  const jwtToken = await signIn(username, pwd);
52
120
  console.log("JWT Token:", jwtToken);
53
121
  });
54
- // Command: deviceInfo
55
- addCommonOptions(
56
- program
57
- .command("deviceInfo")
58
- .description("Retrieve device info for a specific MAC address")
59
- .requiredOption("-m, --mac <macAddress>", "MAC address of the device")
60
- ).action(async (options) => {
61
- const { username, password, mac } = options;
62
- const pwd = password || (await promptPassword());
63
- const jwtToken = await signIn(username, pwd);
64
- const api = configure(); // Use the default API configuration
65
- const deviceInfo = await api.deviceInfo(jwtToken, mac);
66
- console.log("Device Info:", deviceInfo.data);
122
+ // Generic getter commands
123
+ [
124
+ {
125
+ commandName: "deviceInfo",
126
+ description: "Retrieve device info for a specific MAC address",
127
+ getter: (
128
+ api: ReturnType<typeof configure>,
129
+ jwtToken: string,
130
+ mac: string
131
+ ) => api.deviceInfo(jwtToken, mac),
132
+ },
133
+ {
134
+ commandName: "getPower",
135
+ description: "Retrieve device power status",
136
+ getter: (
137
+ api: ReturnType<typeof configure>,
138
+ jwtToken: string,
139
+ mac: string
140
+ ) => api.getPower(jwtToken, mac),
141
+ },
142
+ {
143
+ commandName: "getEnvironmentTemperature",
144
+ description: "Retrieve environment temperature",
145
+ getter: (
146
+ api: ReturnType<typeof configure>,
147
+ jwtToken: string,
148
+ mac: string
149
+ ) => api.getEnvironmentTemperature(jwtToken, mac),
150
+ },
151
+ {
152
+ commandName: "getTargetTemperature",
153
+ description: "Retrieve target temperature",
154
+ getter: (
155
+ api: ReturnType<typeof configure>,
156
+ jwtToken: string,
157
+ mac: string
158
+ ) => api.getTargetTemperature(jwtToken, mac),
159
+ },
160
+ ].forEach(({ commandName, description, getter }) => {
161
+ addMacOption(
162
+ addAuthOptions(program.command(commandName).description(description))
163
+ ).action((options) => executeGetter(options, getter));
67
164
  });
165
+ // Generic setter commands
166
+ [
167
+ {
168
+ commandName: "setPower",
169
+ description: "Set the power state of the device (1 for ON, 0 for OFF)",
170
+ setter: (
171
+ api: ReturnType<typeof configure>,
172
+ jwtToken: string,
173
+ mac: string,
174
+ value: number
175
+ ) => api.setPower(jwtToken, mac, value),
176
+ },
177
+ {
178
+ commandName: "setTargetTemperature",
179
+ description: "Set the target temperature (degree celsius) for a device",
180
+ setter: (
181
+ api: ReturnType<typeof configure>,
182
+ jwtToken: string,
183
+ mac: string,
184
+ value: number
185
+ ) => api.setTargetTemperature(jwtToken, mac, value),
186
+ },
187
+ ].forEach(({ commandName, description, setter }) => {
188
+ addMacOption(
189
+ addAuthOptions(
190
+ program.command(commandName).description(description)
191
+ ).requiredOption("-v, --value <number>", "Value to set", parseFloat)
192
+ ).action((options) => executeSetter(options, setter));
193
+ });
194
+
68
195
  return program;
69
196
  };
70
197
 
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { configure } from "./library";
2
2
 
3
3
  export { API_URL } from "./constants";
4
-
4
+ export { configure, signIn } from "./library";
5
5
  export {
6
6
  CommandsType,
7
7
  DeviceInfoType,
@@ -10,6 +10,13 @@ export {
10
10
  UserParametersType,
11
11
  } from "./types";
12
12
 
13
- export { signIn, configure } from "./library";
14
-
15
- export const { deviceInfo, setPower, setPowerOff, setPowerOn } = configure();
13
+ export const {
14
+ deviceInfo,
15
+ setPower,
16
+ setPowerOff,
17
+ setPowerOn,
18
+ getPower,
19
+ getEnvironmentTemperature,
20
+ getTargetTemperature,
21
+ setTargetTemperature,
22
+ } = configure();