miravo 0.6.0 → 0.6.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/LICENSE +199 -0
- package/README.md +29 -59
- package/bin/miravo.js +84 -0
- package/package.json +21 -37
- package/console-dist/assets/activity-DsWeRq4h.js +0 -1
- package/console-dist/assets/api-4AqKvsm9.js +0 -1
- package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
- package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
- package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
- package/console-dist/assets/box-C1WaoOMF.js +0 -1
- package/console-dist/assets/card-BjeP32PN.js +0 -1
- package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
- package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
- package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
- package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
- package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
- package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
- package/console-dist/assets/control-CPhvwx9o.js +0 -1
- package/console-dist/assets/control-Cg2up9NA.js +0 -1
- package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
- package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
- package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
- package/console-dist/assets/dist-BhbDML0a.js +0 -1
- package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
- package/console-dist/assets/download-DQ6NH16U.js +0 -1
- package/console-dist/assets/engine-store-CrLnz692.js +0 -1
- package/console-dist/assets/events-CXm29Ok4.js +0 -1
- package/console-dist/assets/events-D01IxhnP.js +0 -1
- package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
- package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
- package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
- package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
- package/console-dist/assets/fleet-lotMGVqe.js +0 -1
- package/console-dist/assets/format-kXTonpDJ.js +0 -1
- package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
- package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
- package/console-dist/assets/index-CVPKSDvJ.js +0 -12
- package/console-dist/assets/index-DJkgDeun.css +0 -2
- package/console-dist/assets/input-BWEQFptx.js +0 -1
- package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
- package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
- package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
- package/console-dist/assets/label-uephX5ZG.js +0 -1
- package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
- package/console-dist/assets/login-C3RJli0m.js +0 -126
- package/console-dist/assets/models-B_UioWQ5.js +0 -1
- package/console-dist/assets/models-_IPlPiZ7.js +0 -1
- package/console-dist/assets/not-found-B8gAm08S.js +0 -1
- package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
- package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
- package/console-dist/assets/platforms-DStNbtVc.js +0 -1
- package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
- package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
- package/console-dist/assets/radio-CoHegOv5.js +0 -1
- package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
- package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/console-dist/assets/select-CeO4vP0G.js +0 -1
- package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
- package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
- package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
- package/console-dist/assets/tags-CiXOIHtY.js +0 -4
- package/console-dist/assets/tags-FQCXIxrP.js +0 -1
- package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
- package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
- package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
- package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
- package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
- package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
- package/console-dist/assets/validation-Blvud9_O.js +0 -1
- package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
- package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
- package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
- package/console-dist/favicon.svg +0 -37
- package/console-dist/index.html +0 -82
- package/console-dist/miravo-logo.png +0 -0
- package/src/__tests__/integration/executable-methods-runner.ts +0 -262
- package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
- package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
- package/src/commands/adapter.ts +0 -249
- package/src/commands/apply.test.ts +0 -62
- package/src/commands/apply.ts +0 -216
- package/src/commands/cleanup.test.ts +0 -34
- package/src/commands/cleanup.ts +0 -201
- package/src/commands/cli-test-setup.ts +0 -91
- package/src/commands/export.test.ts +0 -51
- package/src/commands/export.ts +0 -404
- package/src/commands/fork.ts +0 -219
- package/src/commands/inject.test.ts +0 -57
- package/src/commands/inject.ts +0 -212
- package/src/commands/load.ts +0 -167
- package/src/commands/logs.ts +0 -173
- package/src/commands/models.test.ts +0 -162
- package/src/commands/models.ts +0 -338
- package/src/commands/pause.ts +0 -67
- package/src/commands/platforms.test.ts +0 -186
- package/src/commands/platforms.ts +0 -371
- package/src/commands/ps.ts +0 -116
- package/src/commands/remove.ts +0 -38
- package/src/commands/reset.ts +0 -44
- package/src/commands/rm.ts +0 -97
- package/src/commands/spawn.ts +0 -45
- package/src/commands/speed.ts +0 -40
- package/src/commands/start/adapter-config.ts +0 -157
- package/src/commands/start/catalog-le-dt.test.ts +0 -50
- package/src/commands/start/catalog-le-dt.ts +0 -187
- package/src/commands/start/catalog-model-schema.ts +0 -89
- package/src/commands/start/catalog-template-detail.ts +0 -54
- package/src/commands/start/daemon.ts +0 -255
- package/src/commands/start/display.ts +0 -150
- package/src/commands/start/engine-setup.ts +0 -247
- package/src/commands/start/events.ts +0 -126
- package/src/commands/start/index.ts +0 -897
- package/src/commands/start/protocol-plans.ts +0 -239
- package/src/commands/start/template-loader.ts +0 -197
- package/src/commands/start/template-resolver.ts +0 -185
- package/src/commands/start/types.ts +0 -73
- package/src/commands/start.test.ts +0 -480
- package/src/commands/status.test.ts +0 -20
- package/src/commands/status.ts +0 -470
- package/src/commands/stop.test.ts +0 -84
- package/src/commands/stop.ts +0 -169
- package/src/commands/templates.test.ts +0 -58
- package/src/commands/templates.ts +0 -277
- package/src/index.ts +0 -166
- package/src/lib/admin-auth.ts +0 -157
- package/src/lib/admin-client.test.ts +0 -51
- package/src/lib/admin-client.ts +0 -326
- package/src/lib/admin-platform-routes.ts +0 -1017
- package/src/lib/admin-platform-stream.test.ts +0 -434
- package/src/lib/admin-platform-stream.ts +0 -788
- package/src/lib/admin-responses.ts +0 -211
- package/src/lib/admin-server.test.ts +0 -781
- package/src/lib/admin-server.ts +0 -797
- package/src/lib/auth-store.ts +0 -87
- package/src/lib/daemon-registry.test.ts +0 -429
- package/src/lib/daemon-registry.ts +0 -343
- package/src/lib/le/le-apply.test.ts +0 -591
- package/src/lib/le/le-apply.ts +0 -832
- package/src/lib/le/le-auth.ts +0 -89
- package/src/lib/le/le-cleanup.test.ts +0 -168
- package/src/lib/le/le-cleanup.ts +0 -399
- package/src/lib/le/le-cli-helpers.ts +0 -77
- package/src/lib/le/le-client.ts +0 -184
- package/src/lib/le/le-concurrency.test.ts +0 -157
- package/src/lib/le/le-concurrency.ts +0 -78
- package/src/lib/le/le-connection-test.ts +0 -42
- package/src/lib/le/le-devicehub-properties.test.ts +0 -53
- package/src/lib/le/le-devicehub-properties.ts +0 -33
- package/src/lib/le/le-devicehub.ts +0 -213
- package/src/lib/le/le-digital-twins.ts +0 -285
- package/src/lib/le/le-drivers.ts +0 -23
- package/src/lib/le/le-endpoint.ts +0 -40
- package/src/lib/le/le-environments.test.ts +0 -73
- package/src/lib/le/le-environments.ts +0 -156
- package/src/lib/le/le-markers.ts +0 -87
- package/src/lib/le/le-plan.test.ts +0 -26
- package/src/lib/le/le-plan.ts +0 -109
- package/src/lib/le/le-platform-operations.test.ts +0 -198
- package/src/lib/le/le-platform-operations.ts +0 -340
- package/src/lib/le/le-static-model-schema.ts +0 -78
- package/src/lib/le/le-static-planner.test.ts +0 -255
- package/src/lib/le/le-static-planner.ts +0 -442
- package/src/lib/le/le-template-exporter.test.ts +0 -527
- package/src/lib/le/le-template-exporter.ts +0 -459
- package/src/lib/le/le-value-types.ts +0 -23
- package/src/lib/mqtt-client.ts +0 -115
- package/src/lib/parse.test.ts +0 -123
- package/src/lib/parse.ts +0 -87
- package/src/lib/resolve-simulation.test.ts +0 -118
- package/src/lib/resolve-simulation.ts +0 -181
- package/src/lib/session-store.ts +0 -110
- package/src/lib/shutdown.test.ts +0 -562
- package/src/lib/shutdown.ts +0 -621
- package/src/lib/template-category.test.ts +0 -71
- package/src/lib/template-category.ts +0 -72
- package/src/lib/template-locator.ts +0 -55
- package/src/lib/template-utils.ts +0 -45
- package/src/lib/ws-hub.ts +0 -281
- package/src/logger.test.ts +0 -172
- package/src/logger.ts +0 -402
- package/src/output.test.ts +0 -64
- package/src/output.ts +0 -160
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { createTmpFileUtils } from "@miravo/core/test-tmp-utils";
|
|
4
|
-
import type { DiscoveredTemplate, MiravoTemplate } from "@miravo/shared";
|
|
5
|
-
import { resetExitCode, runCliCommand, withEnvVar } from "./cli-test-setup.ts";
|
|
6
|
-
|
|
7
|
-
const { writeTmpFile, cleanupTmpDir } = createTmpFileUtils(
|
|
8
|
-
resolve(import.meta.dir, ".tmp-test-start"),
|
|
9
|
-
);
|
|
10
|
-
const builtinTemplatesDir = resolve(import.meta.dir, "..", "..", "..", "content", "templates");
|
|
11
|
-
const stressTemplatesDir = resolve(
|
|
12
|
-
import.meta.dir,
|
|
13
|
-
"..",
|
|
14
|
-
"..",
|
|
15
|
-
"..",
|
|
16
|
-
"..",
|
|
17
|
-
"tests",
|
|
18
|
-
"fixtures",
|
|
19
|
-
"stress",
|
|
20
|
-
);
|
|
21
|
-
const BUILTIN_TEMPLATE_PATHS = [
|
|
22
|
-
resolve(builtinTemplatesDir, "smart-factory.miravo.yaml"),
|
|
23
|
-
resolve(builtinTemplatesDir, "water-treatment.miravo.yaml"),
|
|
24
|
-
resolve(builtinTemplatesDir, "commercial-building.miravo.yaml"),
|
|
25
|
-
resolve(builtinTemplatesDir, "backup-power.miravo.yaml"),
|
|
26
|
-
resolve(stressTemplatesDir, "stress-5k.miravo.yaml"),
|
|
27
|
-
resolve(stressTemplatesDir, "stress-10k.miravo.yaml"),
|
|
28
|
-
resolve(stressTemplatesDir, "stress-50k.miravo.yaml"),
|
|
29
|
-
resolve(stressTemplatesDir, "stress-100k.miravo.yaml"),
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
beforeEach(resetExitCode);
|
|
33
|
-
afterEach(async () => {
|
|
34
|
-
resetExitCode();
|
|
35
|
-
await cleanupTmpDir();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const MINIMAL_MODEL = `schema_version: "2"
|
|
39
|
-
model:
|
|
40
|
-
name: test-sensor
|
|
41
|
-
members:
|
|
42
|
-
temperature:
|
|
43
|
-
kind: variable
|
|
44
|
-
data_type: Double
|
|
45
|
-
access: read
|
|
46
|
-
unit: degC
|
|
47
|
-
generator:
|
|
48
|
-
type: noise
|
|
49
|
-
center: 25
|
|
50
|
-
stddev: 2
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
const MINIMAL_TEMPLATE = (modelName: string) => `kind: MiravoTemplate
|
|
54
|
-
version: "1"
|
|
55
|
-
name: test-template
|
|
56
|
-
areas:
|
|
57
|
-
- name: area1
|
|
58
|
-
lines:
|
|
59
|
-
- name: line1
|
|
60
|
-
twins:
|
|
61
|
-
- model: ${modelName}
|
|
62
|
-
count: 1
|
|
63
|
-
`;
|
|
64
|
-
|
|
65
|
-
function discoveredTemplate(id: string, name: string, description?: string): DiscoveredTemplate {
|
|
66
|
-
return {
|
|
67
|
-
path: `/tmp/${id}.miravo.yaml`,
|
|
68
|
-
id,
|
|
69
|
-
name,
|
|
70
|
-
...(description !== undefined ? { description } : {}),
|
|
71
|
-
areaCount: 1,
|
|
72
|
-
twinAssignmentCount: 1,
|
|
73
|
-
inlineModelCount: 0,
|
|
74
|
-
uniqueModelCount: 2,
|
|
75
|
-
modelNames: ["model-a", "model-b"],
|
|
76
|
-
totalTwinCount: 5,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
describe("miravo start", () => {
|
|
81
|
-
// We test the internal logic by importing the createProgram and
|
|
82
|
-
// using Commander's parseAsync with exitOverride to prevent process.exit
|
|
83
|
-
|
|
84
|
-
test("--template with nonexistent path sets exit code 1", async () => {
|
|
85
|
-
await runCliCommand(["start", "--template", "/nonexistent/path.miravo.yaml"]);
|
|
86
|
-
expect(process.exitCode).toBe(1);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("--dry-run validates and exits without starting engine", async () => {
|
|
90
|
-
const modelPath = await writeTmpFile("test-sensor.twin.yaml", MINIMAL_MODEL);
|
|
91
|
-
const modelDir = resolve(modelPath, "..");
|
|
92
|
-
const templatePath = await writeTmpFile("test.miravo.yaml", MINIMAL_TEMPLATE("test-sensor"));
|
|
93
|
-
|
|
94
|
-
await runCliCommand([
|
|
95
|
-
"start",
|
|
96
|
-
"--template",
|
|
97
|
-
templatePath,
|
|
98
|
-
"--models-dir",
|
|
99
|
-
modelDir,
|
|
100
|
-
"--name",
|
|
101
|
-
"test-sim",
|
|
102
|
-
"--dry-run",
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
|
-
// Dry-run should not set error exit code (stays at 0 baseline)
|
|
106
|
-
expect(process.exitCode).toBe(0);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("invalid template YAML sets exit code 1", async () => {
|
|
110
|
-
const templatePath = await writeTmpFile("bad.miravo.yaml", "not: valid: yaml: [");
|
|
111
|
-
await runCliCommand(["start", "--template", templatePath]);
|
|
112
|
-
expect(process.exitCode).toBe(1);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("invalid MIRAVO_MQTT_PORT env var sets exit code 1", async () => {
|
|
116
|
-
await withEnvVar("MIRAVO_MQTT_PORT", "abc", async () => {
|
|
117
|
-
await runCliCommand(["start", "--dry-run"]);
|
|
118
|
-
expect(process.exitCode).toBe(1);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("invalid MIRAVO_ADMIN_PORT env var sets exit code 1", async () => {
|
|
123
|
-
await withEnvVar("MIRAVO_ADMIN_PORT", "NaN", async () => {
|
|
124
|
-
await runCliCommand(["start", "--dry-run"]);
|
|
125
|
-
expect(process.exitCode).toBe(1);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test.each(
|
|
130
|
-
BUILTIN_TEMPLATE_PATHS,
|
|
131
|
-
)("built-in template dry-run succeeds: %s", async (templatePath) => {
|
|
132
|
-
await runCliCommand(["start", "--template", templatePath, "--dry-run"]);
|
|
133
|
-
expect(process.exitCode).toBe(0);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
function createTemplateWithNamespace(namespace?: MiravoTemplate["namespace"]): MiravoTemplate {
|
|
138
|
-
return {
|
|
139
|
-
kind: "MiravoTemplate",
|
|
140
|
-
version: "1",
|
|
141
|
-
name: "test-template",
|
|
142
|
-
namespace,
|
|
143
|
-
areas: [
|
|
144
|
-
{
|
|
145
|
-
name: "area1",
|
|
146
|
-
lines: [
|
|
147
|
-
{
|
|
148
|
-
name: "line1",
|
|
149
|
-
twins: [{ model: "test-sensor", count: 1, variation: 0 }],
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
describe("resolveSimulationName", () => {
|
|
158
|
-
test("priority: --name > MIRAVO_NAME > namespace", async () => {
|
|
159
|
-
const { resolveSimulationName } = await import("./start/index.ts");
|
|
160
|
-
const template = createTemplateWithNamespace({ enterprise: "acme", site: "site-a" });
|
|
161
|
-
|
|
162
|
-
expect(
|
|
163
|
-
resolveSimulationName({
|
|
164
|
-
template,
|
|
165
|
-
cliName: "cli-name",
|
|
166
|
-
envName: "env-name",
|
|
167
|
-
}),
|
|
168
|
-
).toBe("cli-name");
|
|
169
|
-
expect(resolveSimulationName({ template, envName: "env-name" })).toBe("env-name");
|
|
170
|
-
expect(resolveSimulationName({ template })).toBe("acme-site-a");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("uses flat namespace prefix when ISA-95 site is not present", async () => {
|
|
174
|
-
const { resolveSimulationName } = await import("./start/index.ts");
|
|
175
|
-
const template = createTemplateWithNamespace({ prefix: "flat-prefix" });
|
|
176
|
-
expect(resolveSimulationName({ template })).toBe("flat-prefix");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("throws when template has no namespace and no explicit name", async () => {
|
|
180
|
-
const { resolveSimulationName } = await import("./start/index.ts");
|
|
181
|
-
const template = createTemplateWithNamespace();
|
|
182
|
-
|
|
183
|
-
expect(() => resolveSimulationName({ template })).toThrow("Template has no namespace.");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("rejects invalid names with special characters", async () => {
|
|
187
|
-
const { resolveSimulationName } = await import("./start/index.ts");
|
|
188
|
-
const template = createTemplateWithNamespace({ prefix: "valid" });
|
|
189
|
-
|
|
190
|
-
expect(() => resolveSimulationName({ template, cliName: "UPPER-CASE" })).toThrow(
|
|
191
|
-
"Invalid simulation name",
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
expect(() => resolveSimulationName({ template, cliName: "has spaces" })).toThrow(
|
|
195
|
-
"Invalid simulation name",
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
expect(() => resolveSimulationName({ template, cliName: "-leading-hyphen" })).toThrow(
|
|
199
|
-
"Invalid simulation name",
|
|
200
|
-
);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("accepts single-character name", async () => {
|
|
204
|
-
const { resolveSimulationName } = await import("./start/index.ts");
|
|
205
|
-
const template = createTemplateWithNamespace({ prefix: "x" });
|
|
206
|
-
expect(resolveSimulationName({ template })).toBe("x");
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
describe("resolveProtocolBootPlans", () => {
|
|
211
|
-
test("preserves restored adapter state when no explicit protocol overrides are provided", async () => {
|
|
212
|
-
const { resolveProtocolBootPlans } = await import("./start/index.ts");
|
|
213
|
-
|
|
214
|
-
expect(
|
|
215
|
-
resolveProtocolBootPlans({
|
|
216
|
-
options: {},
|
|
217
|
-
optionSources: {},
|
|
218
|
-
restoredFromPersistence: true,
|
|
219
|
-
}),
|
|
220
|
-
).toEqual([
|
|
221
|
-
{ name: "mqtt", shouldEnable: true, overrideRestore: false },
|
|
222
|
-
{ name: "opcua", shouldEnable: false, overrideRestore: false },
|
|
223
|
-
{ name: "modbus", shouldEnable: false, overrideRestore: false },
|
|
224
|
-
]);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test("applies boot defaults when no persisted snapshot was restored", async () => {
|
|
228
|
-
const { resolveProtocolBootPlans } = await import("./start/index.ts");
|
|
229
|
-
|
|
230
|
-
expect(
|
|
231
|
-
resolveProtocolBootPlans({
|
|
232
|
-
options: {},
|
|
233
|
-
optionSources: {},
|
|
234
|
-
restoredFromPersistence: false,
|
|
235
|
-
}),
|
|
236
|
-
).toEqual([
|
|
237
|
-
{ name: "mqtt", shouldEnable: true, overrideRestore: true },
|
|
238
|
-
{ name: "opcua", shouldEnable: false, overrideRestore: true },
|
|
239
|
-
{ name: "modbus", shouldEnable: false, overrideRestore: true },
|
|
240
|
-
]);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("treats explicit MQTT disable as authoritative over persisted state", async () => {
|
|
244
|
-
const { resolveProtocolBootPlans } = await import("./start/index.ts");
|
|
245
|
-
|
|
246
|
-
expect(
|
|
247
|
-
resolveProtocolBootPlans({
|
|
248
|
-
options: { mqtt: false },
|
|
249
|
-
optionSources: { mqtt: "cli" },
|
|
250
|
-
restoredFromPersistence: true,
|
|
251
|
-
}),
|
|
252
|
-
).toContainEqual({ name: "mqtt", shouldEnable: false, overrideRestore: true });
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test("treats explicit CLI MQTT config overrides as authoritative over persisted config", async () => {
|
|
256
|
-
const { resolveProtocolBootPlans } = await import("./start/index.ts");
|
|
257
|
-
|
|
258
|
-
expect(
|
|
259
|
-
resolveProtocolBootPlans({
|
|
260
|
-
options: { mqttPort: 1884 },
|
|
261
|
-
optionSources: { mqttPort: "cli" },
|
|
262
|
-
restoredFromPersistence: true,
|
|
263
|
-
}),
|
|
264
|
-
).toContainEqual({ name: "mqtt", shouldEnable: true, overrideRestore: true });
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test("does not treat env fallback config as authoritative over persisted MQTT state", async () => {
|
|
268
|
-
const { resolveProtocolBootPlans } = await import("./start/index.ts");
|
|
269
|
-
|
|
270
|
-
expect(
|
|
271
|
-
resolveProtocolBootPlans({
|
|
272
|
-
options: {},
|
|
273
|
-
optionSources: {},
|
|
274
|
-
restoredFromPersistence: true,
|
|
275
|
-
}),
|
|
276
|
-
).toContainEqual({ name: "mqtt", shouldEnable: true, overrideRestore: false });
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test("treats explicit OPC UA enable as authoritative over persisted state", async () => {
|
|
280
|
-
const { resolveProtocolBootPlans } = await import("./start/index.ts");
|
|
281
|
-
|
|
282
|
-
expect(
|
|
283
|
-
resolveProtocolBootPlans({
|
|
284
|
-
options: { opcua: true },
|
|
285
|
-
optionSources: { opcua: "cli" },
|
|
286
|
-
restoredFromPersistence: true,
|
|
287
|
-
}),
|
|
288
|
-
).toContainEqual({ name: "opcua", shouldEnable: true, overrideRestore: true });
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
test("does not treat ignored OPC UA config flags as persisted-state overrides", async () => {
|
|
292
|
-
const { resolveProtocolBootPlans } = await import("./start/index.ts");
|
|
293
|
-
|
|
294
|
-
expect(
|
|
295
|
-
resolveProtocolBootPlans({
|
|
296
|
-
options: { opcuaPort: 4841 },
|
|
297
|
-
optionSources: { opcuaPort: "cli" },
|
|
298
|
-
restoredFromPersistence: true,
|
|
299
|
-
}),
|
|
300
|
-
).toContainEqual({ name: "opcua", shouldEnable: false, overrideRestore: false });
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
describe("resolveDetachAdapterConfigs", () => {
|
|
305
|
-
test("uses template adapter configs when no CLI/env overrides are provided", async () => {
|
|
306
|
-
const { resolveDetachAdapterConfigs } = await import("./start/index.ts");
|
|
307
|
-
|
|
308
|
-
const template = createTemplateWithNamespace({ prefix: "factory" });
|
|
309
|
-
const withProtocols: MiravoTemplate = {
|
|
310
|
-
...template,
|
|
311
|
-
protocols: {
|
|
312
|
-
mqtt: { port: 1889, embedded: true, retain: false, metadata: "base" },
|
|
313
|
-
opcua: { port: 4851, host: "127.0.0.1", security: "none" },
|
|
314
|
-
modbus: { port: 1602, host: "127.0.0.1" },
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
expect(
|
|
319
|
-
resolveDetachAdapterConfigs({
|
|
320
|
-
template: withProtocols,
|
|
321
|
-
simulationName: "factory-sim",
|
|
322
|
-
mqtt: { enabled: true, portOverride: undefined, overrides: {} },
|
|
323
|
-
opcua: {
|
|
324
|
-
enabled: true,
|
|
325
|
-
portOverride: undefined,
|
|
326
|
-
hostOverride: undefined,
|
|
327
|
-
writeBudgetOverride: undefined,
|
|
328
|
-
},
|
|
329
|
-
modbus: { enabled: true, portOverride: undefined, hostOverride: undefined },
|
|
330
|
-
}),
|
|
331
|
-
).toEqual({
|
|
332
|
-
mqtt: { port: 1889, host: "0.0.0.0", embedded: true },
|
|
333
|
-
opcua: { port: 4851, host: "127.0.0.1" },
|
|
334
|
-
modbus: { port: 1602, host: "127.0.0.1" },
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("CLI/env overrides take precedence over template values", async () => {
|
|
339
|
-
const { resolveDetachAdapterConfigs } = await import("./start/index.ts");
|
|
340
|
-
|
|
341
|
-
const template = createTemplateWithNamespace({ prefix: "factory" });
|
|
342
|
-
const withProtocols: MiravoTemplate = {
|
|
343
|
-
...template,
|
|
344
|
-
protocols: {
|
|
345
|
-
mqtt: { port: 1889, embedded: true, retain: false, metadata: "base" },
|
|
346
|
-
opcua: { port: 4851, host: "127.0.0.1", security: "none" },
|
|
347
|
-
modbus: { port: 1602, host: "127.0.0.1" },
|
|
348
|
-
},
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
expect(
|
|
352
|
-
resolveDetachAdapterConfigs({
|
|
353
|
-
template: withProtocols,
|
|
354
|
-
simulationName: "factory-sim",
|
|
355
|
-
mqtt: {
|
|
356
|
-
enabled: true,
|
|
357
|
-
portOverride: 1999,
|
|
358
|
-
overrides: { host: "0.0.0.0" },
|
|
359
|
-
},
|
|
360
|
-
opcua: {
|
|
361
|
-
enabled: true,
|
|
362
|
-
portOverride: 4852,
|
|
363
|
-
hostOverride: "0.0.0.0",
|
|
364
|
-
writeBudgetOverride: undefined,
|
|
365
|
-
},
|
|
366
|
-
modbus: { enabled: true, portOverride: 1702, hostOverride: "0.0.0.0" },
|
|
367
|
-
}),
|
|
368
|
-
).toEqual({
|
|
369
|
-
mqtt: { port: 1999, host: "0.0.0.0", embedded: true },
|
|
370
|
-
opcua: { port: 4852, host: "0.0.0.0" },
|
|
371
|
-
modbus: { port: 1702, host: "0.0.0.0" },
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
test("returns disabled placeholders when adapters are disabled", async () => {
|
|
376
|
-
const { resolveDetachAdapterConfigs } = await import("./start/index.ts");
|
|
377
|
-
const template = createTemplateWithNamespace({ prefix: "factory" });
|
|
378
|
-
|
|
379
|
-
expect(
|
|
380
|
-
resolveDetachAdapterConfigs({
|
|
381
|
-
template,
|
|
382
|
-
simulationName: "factory-sim",
|
|
383
|
-
mqtt: { enabled: false, portOverride: 1999, overrides: { host: "0.0.0.0" } },
|
|
384
|
-
opcua: {
|
|
385
|
-
enabled: false,
|
|
386
|
-
portOverride: 4852,
|
|
387
|
-
hostOverride: "0.0.0.0",
|
|
388
|
-
writeBudgetOverride: 4096,
|
|
389
|
-
},
|
|
390
|
-
modbus: { enabled: false, portOverride: 1702, hostOverride: "0.0.0.0" },
|
|
391
|
-
}),
|
|
392
|
-
).toEqual({
|
|
393
|
-
mqtt: { port: undefined, host: undefined, embedded: false },
|
|
394
|
-
opcua: { port: undefined, host: undefined },
|
|
395
|
-
modbus: { port: undefined, host: undefined },
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test("marks external MQTT mode as non-embedded for detach conflict checks", async () => {
|
|
400
|
-
const { resolveDetachAdapterConfigs } = await import("./start/index.ts");
|
|
401
|
-
const template = createTemplateWithNamespace({ prefix: "factory" });
|
|
402
|
-
|
|
403
|
-
expect(
|
|
404
|
-
resolveDetachAdapterConfigs({
|
|
405
|
-
template,
|
|
406
|
-
simulationName: "factory-sim",
|
|
407
|
-
mqtt: {
|
|
408
|
-
enabled: true,
|
|
409
|
-
portOverride: undefined,
|
|
410
|
-
overrides: { mode: "external", url: "mqtt://broker.example.com:1883" },
|
|
411
|
-
},
|
|
412
|
-
opcua: {
|
|
413
|
-
enabled: false,
|
|
414
|
-
portOverride: undefined,
|
|
415
|
-
hostOverride: undefined,
|
|
416
|
-
writeBudgetOverride: undefined,
|
|
417
|
-
},
|
|
418
|
-
modbus: { enabled: false, portOverride: undefined, hostOverride: undefined },
|
|
419
|
-
}).mqtt.embedded,
|
|
420
|
-
).toBe(false);
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
describe("error context aggregation", () => {
|
|
425
|
-
test("normalizeErrorContext redacts high-cardinality fragments", async () => {
|
|
426
|
-
const { normalizeErrorContext } = await import("./start/index.ts");
|
|
427
|
-
|
|
428
|
-
expect(normalizeErrorContext('generator="rpm" instance="pump-001" tick=42')).toBe(
|
|
429
|
-
'generator="rpm" instance="*" tick=*',
|
|
430
|
-
);
|
|
431
|
-
expect(normalizeErrorContext('spawn:unknown-parameter model="pump-model"')).toBe(
|
|
432
|
-
'spawn:unknown-parameter model="*"',
|
|
433
|
-
);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
test("incrementErrorContextCount caps unique keys and accumulates in overflow bucket", async () => {
|
|
437
|
-
const { incrementErrorContextCount } = await import("./start/index.ts");
|
|
438
|
-
const counts = new Map<string, number>();
|
|
439
|
-
|
|
440
|
-
for (let i = 0; i < 256; i++) {
|
|
441
|
-
incrementErrorContextCount(counts, `context-${i}`);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
incrementErrorContextCount(counts, "context-256");
|
|
445
|
-
incrementErrorContextCount(counts, "context-257");
|
|
446
|
-
|
|
447
|
-
expect(counts.size).toBe(257);
|
|
448
|
-
expect(counts.get("__overflow__")).toBe(2);
|
|
449
|
-
expect(counts.get("context-0")).toBe(1);
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
describe("template picker choices", () => {
|
|
454
|
-
test("buildBuiltinTemplateChoices renders tree labels directly in select options", async () => {
|
|
455
|
-
const { buildBuiltinTemplateChoices } = await import("./start/index.ts");
|
|
456
|
-
const choices = buildBuiltinTemplateChoices([
|
|
457
|
-
discoveredTemplate("stress-50k", "Stress Benchmark 50K"),
|
|
458
|
-
discoveredTemplate("water-treatment", "Water Treatment Plant"),
|
|
459
|
-
discoveredTemplate(
|
|
460
|
-
"smart-factory",
|
|
461
|
-
"Smart Factory Production Line",
|
|
462
|
-
"Discrete manufacturing line",
|
|
463
|
-
),
|
|
464
|
-
discoveredTemplate("stress-10k", "Stress Benchmark 10K"),
|
|
465
|
-
]);
|
|
466
|
-
|
|
467
|
-
expect(choices.map((choice) => choice.label)).toEqual([
|
|
468
|
-
"Industrial Scenarios",
|
|
469
|
-
" \u251C\u2500 Smart Factory Production Line",
|
|
470
|
-
" \u2514\u2500 Water Treatment Plant",
|
|
471
|
-
"Stress & Benchmark",
|
|
472
|
-
" \u251C\u2500 Stress Benchmark 10K",
|
|
473
|
-
" \u2514\u2500 Stress Benchmark 50K",
|
|
474
|
-
]);
|
|
475
|
-
expect(choices[0]?.disabled).toBe(true);
|
|
476
|
-
expect(choices[1]?.disabled).toBeUndefined();
|
|
477
|
-
expect(choices[1]?.hint).toContain("Discrete manufacturing line");
|
|
478
|
-
expect(choices[4]?.hint).toContain("stress-10k");
|
|
479
|
-
});
|
|
480
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { findCliCommand, resetExitCode } from "./cli-test-setup.ts";
|
|
3
|
-
|
|
4
|
-
beforeEach(resetExitCode);
|
|
5
|
-
afterEach(resetExitCode);
|
|
6
|
-
|
|
7
|
-
describe("miravo status", () => {
|
|
8
|
-
test("sets exit code 1 with invalid admin URL", async () => {
|
|
9
|
-
const { program } = await findCliCommand("status");
|
|
10
|
-
program.exitOverride();
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
await program.parseAsync(["node", "miravo", "status", "--admin-url", "not-a-url"]);
|
|
14
|
-
} catch {
|
|
15
|
-
// Expected
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
expect(process.exitCode).toBe(1);
|
|
19
|
-
});
|
|
20
|
-
});
|