Skip to content

Commit 28c8a6f

Browse files
committed
test(cli): cover new functionalities with basic tests
1 parent 1f1d88e commit 28c8a6f

13 files changed

Lines changed: 703 additions & 157 deletions

packages/cli/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
src/generated
2+
test/resources

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"lint": "eslint .",
4141
"lint:fix": "eslint . --fix",
4242
"format": "prettier --write \"src/**/*.ts\" \"**/*.json\"",
43-
"test": "mocha --require ts-node/register --extension ts"
43+
"test": "mocha --recursive --require ts-node/register --extension ts"
4444
},
4545
"bugs": {
4646
"url": "https://github.com/eclipse-thingweb/node-wot/issues"

packages/cli/src/utils/string-to-js-value.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
* @returns The converted value as Number, Boolean, or String.
2222
*/
2323
export function stringToJSValue(value: string) {
24-
if (Number(value)) {
24+
if (!isNaN(Number(value))) {
2525
return +value;
2626
} else if (value === "true" || value === "false") {
27-
return Boolean(value);
27+
return value === "true";
2828
} else {
2929
return value;
3030
}

packages/cli/test/configuration.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License v. 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
10+
* Document License (2015-05-13) which is available at
11+
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
12+
*
13+
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
14+
********************************************************************************/
15+
16+
import { suite, test } from "@testdeck/mocha";
17+
import { should, expect, use as chaiUse } from "chai";
18+
import { buildConfig, buildConfigFromFile, defaultConfiguration, Configuration } from "../src/configuration";
19+
import Ajv, { ValidateFunction } from "ajv";
20+
import ConfigSchema from "../src/generated/wot-servient-schema.conf";
21+
import chaiAsPromised from "chai-as-promised";
22+
import { writeFileSync, unlinkSync } from "fs";
23+
import { join } from "path";
24+
import { ValidationError } from "ajv";
25+
26+
should();
27+
chaiUse(chaiAsPromised);
28+
29+
@suite("Configuration management")
30+
class ConfigurationTest {
31+
private static validator: ValidateFunction<Configuration>;
32+
private testFilePath!: string;
33+
34+
static before() {
35+
const ajv = new Ajv({ strict: true, allErrors: true });
36+
ConfigurationTest.validator = ajv.compile(ConfigSchema) as ValidateFunction<Configuration>;
37+
}
38+
39+
before() {
40+
this.testFilePath = join(__dirname, "./resources", "test-config-" + Date.now() + ".json");
41+
}
42+
43+
after() {
44+
try {
45+
unlinkSync(this.testFilePath);
46+
} catch {
47+
// File may not exist
48+
}
49+
}
50+
51+
@test async "should use default configuration when none provided"() {
52+
const result = await buildConfig({}, defaultConfiguration, {}, ConfigurationTest.validator);
53+
54+
expect(result).to.have.property("http");
55+
expect(result.http.port).to.equal(8080);
56+
expect(result.coap.port).to.equal(5683);
57+
expect(result.logLevel).to.equal("warn");
58+
}
59+
60+
@test async "should handle credentials in config"() {
61+
const config = { ...defaultConfiguration, credentials: { THING_ID_1: { username: "user", password: "pass" } } };
62+
const result = await buildConfig({}, config, {}, ConfigurationTest.validator);
63+
64+
expect(result.credentials).to.have.property("THING_ID_1");
65+
}
66+
67+
@test async "should merge environment variables with defaults"() {
68+
const env = { HTTP_PORT: "9000" };
69+
const result = await buildConfig({}, defaultConfiguration, env, ConfigurationTest.validator);
70+
71+
expect(result.http.port).to.equal(9000);
72+
}
73+
74+
@test async "should apply config parameters"() {
75+
const options = { configParams: { http: { port: 8888 } } };
76+
const result = await buildConfig(options, defaultConfiguration, {}, ConfigurationTest.validator);
77+
78+
expect(result.http.port).to.equal(8888);
79+
}
80+
81+
@test async "should merge environment variables and config parameters"() {
82+
const env = { HTTP_PORT: "9000" };
83+
const options = { configParams: { coap: { port: 6000 } } };
84+
const result = await buildConfig(options, defaultConfiguration, env, ConfigurationTest.validator);
85+
86+
expect(result.http.port).to.equal(9000);
87+
expect(result.coap.port).to.equal(6000);
88+
}
89+
90+
@test "should validate merged configuration"() {
91+
const options = { configParams: { http: { port: "invalid" } } };
92+
93+
expect(buildConfig(options, defaultConfiguration, {}, ConfigurationTest.validator)).to.eventually.throw(
94+
ValidationError
95+
);
96+
}
97+
98+
@test async "should apply default values to provided config"() {
99+
const customConfig = { http: { port: 8888 } };
100+
const result = await buildConfig({}, customConfig, {}, ConfigurationTest.validator);
101+
102+
expect(result.http.port).to.equal(8888);
103+
expect(result.coap.port).to.equal(defaultConfiguration.coap.port);
104+
expect(result.logLevel).to.equal(defaultConfiguration.logLevel);
105+
}
106+
107+
@test async "should read and build config from file"() {
108+
const config = { http: { port: 7777 } };
109+
writeFileSync(this.testFilePath, JSON.stringify(config));
110+
111+
const result = await buildConfigFromFile({}, this.testFilePath, {}, ConfigurationTest.validator);
112+
113+
expect(result.http.port).to.equal(7777);
114+
}
115+
116+
@test async "should merge file config with environment variables"() {
117+
const config = { http: { port: 7777 } };
118+
writeFileSync(this.testFilePath, JSON.stringify(config));
119+
120+
const env = { COAP_PORT: "6000" };
121+
const result = await buildConfigFromFile({}, this.testFilePath, env, ConfigurationTest.validator);
122+
123+
expect(result.http.port).to.equal(7777);
124+
expect(result.coap.port).to.equal(6000);
125+
}
126+
127+
@test async "should handle configFile option"() {
128+
const config = { http: { port: 5555 } };
129+
writeFileSync(this.testFilePath, JSON.stringify(config));
130+
131+
const options = { configFile: this.testFilePath };
132+
const result = await buildConfigFromFile(options, this.testFilePath, {}, ConfigurationTest.validator);
133+
134+
expect(result.http.port).to.equal(5555);
135+
}
136+
137+
@test "should throw error for invalid config file"() {
138+
writeFileSync(this.testFilePath, "{ invalid json }");
139+
140+
expect(buildConfigFromFile({}, this.testFilePath, {}, ConfigurationTest.validator)).to.eventually.throw();
141+
}
142+
143+
@test async "should convert string env variables to appropriate types"() {
144+
const env = { HTTP_PORT: "8080", SERVIENT_CLIENTONLY: "true", COAP_PORT: "5683" };
145+
const result = await buildConfig({}, defaultConfiguration, env, ConfigurationTest.validator);
146+
147+
expect(result.http.port).to.equal(8080);
148+
expect(result.servient.clientOnly).to.equal(true);
149+
expect(result.coap.port).to.equal(5683);
150+
}
151+
}

packages/cli/test/executor.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License v. 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
10+
* Document License (2015-05-13) which is available at
11+
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
12+
*
13+
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
14+
********************************************************************************/
15+
16+
import { suite, test } from "@testdeck/mocha";
17+
import { should, expect } from "chai";
18+
import { Executor, WoTContext } from "../src/executor";
19+
import { writeFileSync, unlinkSync } from "fs";
20+
import { join } from "path";
21+
import { Helpers } from "@node-wot/core";
22+
23+
should();
24+
25+
@suite("Executor")
26+
class ExecutorTest {
27+
private executor!: Executor;
28+
private testFilePath!: string;
29+
private mockWoTContext!: WoTContext;
30+
31+
before() {
32+
this.executor = new Executor();
33+
this.testFilePath = join(__dirname, "./resources", "test-script-" + Date.now());
34+
this.mockWoTContext = {
35+
// We are not using WoT inside this testing scripts
36+
runtime: {} as typeof WoT,
37+
helpers: {} as Helpers,
38+
};
39+
}
40+
41+
after() {
42+
try {
43+
unlinkSync(this.testFilePath + ".js");
44+
} catch {
45+
// File may not exist
46+
}
47+
try {
48+
unlinkSync(this.testFilePath + ".mjs");
49+
} catch {
50+
// File may not exist
51+
}
52+
try {
53+
unlinkSync(this.testFilePath + ".ts");
54+
} catch {
55+
// File may not exist
56+
}
57+
try {
58+
unlinkSync(this.testFilePath + ".tsx");
59+
} catch {
60+
// File may not exist
61+
}
62+
}
63+
64+
@test async "should execute JavaScript file"() {
65+
const scriptContent = "module.exports = 'test result';";
66+
writeFileSync(this.testFilePath + ".js", scriptContent);
67+
68+
const result = await this.executor.exec(this.testFilePath + ".js", this.mockWoTContext);
69+
70+
expect(result).to.equal("test result");
71+
}
72+
73+
@test async "should have WoT defined"() {
74+
const scriptContent = "module.exports = typeof global.WoT !== 'undefined';";
75+
writeFileSync(this.testFilePath + ".js", scriptContent);
76+
77+
const result = await this.executor.exec(this.testFilePath + ".js", this.mockWoTContext);
78+
79+
expect(result).to.be.true;
80+
}
81+
82+
@test async "should handle module exports"() {
83+
const scriptContent = "module.exports = { message: 'hello' };";
84+
writeFileSync(this.testFilePath + ".js", scriptContent);
85+
86+
const result = await this.executor.exec(this.testFilePath + ".js", this.mockWoTContext);
87+
88+
expect(result).to.have.property("message", "hello");
89+
}
90+
91+
@test async "should detect TypeScript files by .ts extension"() {
92+
const scriptContent = "export const value: number = 42;";
93+
writeFileSync(this.testFilePath + ".ts", scriptContent);
94+
95+
const { value } = (await this.executor.exec(this.testFilePath + ".ts", this.mockWoTContext)) as {
96+
value: number;
97+
};
98+
99+
expect(value).to.be.eq(42);
100+
}
101+
102+
@test async "should handle .mjs files as ES modules"() {
103+
const filePath = this.testFilePath + ".mjs";
104+
const scriptContent = "export const value = 'es module';";
105+
writeFileSync(filePath, scriptContent);
106+
107+
const { value } = (await this.executor.exec(filePath, this.mockWoTContext)) as { value: string };
108+
expect(value).to.be.eq("es module");
109+
}
110+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License v. 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
10+
* Document License (2015-05-13) which is available at
11+
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
12+
*
13+
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
14+
********************************************************************************/
15+
16+
import { suite, test } from "@testdeck/mocha";
17+
import { should, expect } from "chai";
18+
import { parseConfigFile } from "../../src/parsers/config-file-parser";
19+
import { InvalidArgumentError } from "commander";
20+
import { writeFileSync, unlinkSync } from "fs";
21+
import { join } from "path";
22+
23+
should();
24+
25+
@suite("parseConfigFile parser")
26+
class ConfigFileParserTest {
27+
private testFilePath!: string;
28+
29+
before() {
30+
this.testFilePath = join(__dirname, "../resources", "test-config-" + Date.now() + ".json");
31+
}
32+
33+
after() {
34+
try {
35+
unlinkSync(this.testFilePath);
36+
} catch {
37+
// File may not exist if test failed before creation
38+
}
39+
}
40+
41+
@test "should parse valid JSON config file"() {
42+
const validConfig = { http: { port: 8080 }, coap: { port: 5683 } };
43+
writeFileSync(this.testFilePath, JSON.stringify(validConfig), { flag: "w+" });
44+
45+
const result = parseConfigFile(this.testFilePath, undefined);
46+
47+
expect(result).to.equal(this.testFilePath);
48+
}
49+
50+
@test "should throw error for invalid JSON"() {
51+
writeFileSync(this.testFilePath, "{ invalid json }");
52+
53+
expect(() => parseConfigFile(this.testFilePath, undefined)).to.throw(InvalidArgumentError);
54+
}
55+
56+
@test "should throw error for non-existent file"() {
57+
expect(() => parseConfigFile("/nonexistent/file.json", undefined)).to.throw(InvalidArgumentError);
58+
}
59+
60+
@test "should throw error for empty JSON object"() {
61+
writeFileSync(this.testFilePath, "{}");
62+
63+
const result = parseConfigFile(this.testFilePath, undefined);
64+
expect(result).to.equal(this.testFilePath);
65+
}
66+
67+
@test "should handle complex JSON structures"() {
68+
const complexConfig = {
69+
servient: { clientOnly: false },
70+
http: { port: 8080, allowSelfSigned: false },
71+
coap: { port: 5683 },
72+
credentials: { user: "admin" },
73+
};
74+
writeFileSync(this.testFilePath, JSON.stringify(complexConfig));
75+
76+
const result = parseConfigFile(this.testFilePath, undefined);
77+
78+
expect(result).to.equal(this.testFilePath);
79+
}
80+
81+
@test "should handle JSON array at root"() {
82+
writeFileSync(this.testFilePath, JSON.stringify([1, 2, 3]));
83+
84+
const result = parseConfigFile(this.testFilePath, undefined);
85+
expect(result).to.equal(this.testFilePath);
86+
}
87+
}

0 commit comments

Comments
 (0)