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.
Files changed (187) hide show
  1. package/LICENSE +199 -0
  2. package/README.md +29 -59
  3. package/bin/miravo.js +84 -0
  4. package/package.json +21 -37
  5. package/console-dist/assets/activity-DsWeRq4h.js +0 -1
  6. package/console-dist/assets/api-4AqKvsm9.js +0 -1
  7. package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
  8. package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
  9. package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
  10. package/console-dist/assets/box-C1WaoOMF.js +0 -1
  11. package/console-dist/assets/card-BjeP32PN.js +0 -1
  12. package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
  13. package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
  14. package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
  15. package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
  16. package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
  17. package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
  18. package/console-dist/assets/control-CPhvwx9o.js +0 -1
  19. package/console-dist/assets/control-Cg2up9NA.js +0 -1
  20. package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
  21. package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
  22. package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
  23. package/console-dist/assets/dist-BhbDML0a.js +0 -1
  24. package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
  25. package/console-dist/assets/download-DQ6NH16U.js +0 -1
  26. package/console-dist/assets/engine-store-CrLnz692.js +0 -1
  27. package/console-dist/assets/events-CXm29Ok4.js +0 -1
  28. package/console-dist/assets/events-D01IxhnP.js +0 -1
  29. package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
  30. package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
  31. package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
  32. package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
  33. package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
  34. package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
  35. package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
  36. package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
  37. package/console-dist/assets/fleet-lotMGVqe.js +0 -1
  38. package/console-dist/assets/format-kXTonpDJ.js +0 -1
  39. package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
  40. package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
  41. package/console-dist/assets/index-CVPKSDvJ.js +0 -12
  42. package/console-dist/assets/index-DJkgDeun.css +0 -2
  43. package/console-dist/assets/input-BWEQFptx.js +0 -1
  44. package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
  45. package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
  46. package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
  47. package/console-dist/assets/label-uephX5ZG.js +0 -1
  48. package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
  49. package/console-dist/assets/login-C3RJli0m.js +0 -126
  50. package/console-dist/assets/models-B_UioWQ5.js +0 -1
  51. package/console-dist/assets/models-_IPlPiZ7.js +0 -1
  52. package/console-dist/assets/not-found-B8gAm08S.js +0 -1
  53. package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
  54. package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
  55. package/console-dist/assets/platforms-DStNbtVc.js +0 -1
  56. package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
  57. package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
  58. package/console-dist/assets/radio-CoHegOv5.js +0 -1
  59. package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
  60. package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  61. package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  62. package/console-dist/assets/select-CeO4vP0G.js +0 -1
  63. package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
  64. package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
  65. package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
  66. package/console-dist/assets/tags-CiXOIHtY.js +0 -4
  67. package/console-dist/assets/tags-FQCXIxrP.js +0 -1
  68. package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
  69. package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
  70. package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
  71. package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
  72. package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
  73. package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
  74. package/console-dist/assets/validation-Blvud9_O.js +0 -1
  75. package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
  76. package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
  77. package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
  78. package/console-dist/favicon.svg +0 -37
  79. package/console-dist/index.html +0 -82
  80. package/console-dist/miravo-logo.png +0 -0
  81. package/src/__tests__/integration/executable-methods-runner.ts +0 -262
  82. package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
  83. package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
  84. package/src/commands/adapter.ts +0 -249
  85. package/src/commands/apply.test.ts +0 -62
  86. package/src/commands/apply.ts +0 -216
  87. package/src/commands/cleanup.test.ts +0 -34
  88. package/src/commands/cleanup.ts +0 -201
  89. package/src/commands/cli-test-setup.ts +0 -91
  90. package/src/commands/export.test.ts +0 -51
  91. package/src/commands/export.ts +0 -404
  92. package/src/commands/fork.ts +0 -219
  93. package/src/commands/inject.test.ts +0 -57
  94. package/src/commands/inject.ts +0 -212
  95. package/src/commands/load.ts +0 -167
  96. package/src/commands/logs.ts +0 -173
  97. package/src/commands/models.test.ts +0 -162
  98. package/src/commands/models.ts +0 -338
  99. package/src/commands/pause.ts +0 -67
  100. package/src/commands/platforms.test.ts +0 -186
  101. package/src/commands/platforms.ts +0 -371
  102. package/src/commands/ps.ts +0 -116
  103. package/src/commands/remove.ts +0 -38
  104. package/src/commands/reset.ts +0 -44
  105. package/src/commands/rm.ts +0 -97
  106. package/src/commands/spawn.ts +0 -45
  107. package/src/commands/speed.ts +0 -40
  108. package/src/commands/start/adapter-config.ts +0 -157
  109. package/src/commands/start/catalog-le-dt.test.ts +0 -50
  110. package/src/commands/start/catalog-le-dt.ts +0 -187
  111. package/src/commands/start/catalog-model-schema.ts +0 -89
  112. package/src/commands/start/catalog-template-detail.ts +0 -54
  113. package/src/commands/start/daemon.ts +0 -255
  114. package/src/commands/start/display.ts +0 -150
  115. package/src/commands/start/engine-setup.ts +0 -247
  116. package/src/commands/start/events.ts +0 -126
  117. package/src/commands/start/index.ts +0 -897
  118. package/src/commands/start/protocol-plans.ts +0 -239
  119. package/src/commands/start/template-loader.ts +0 -197
  120. package/src/commands/start/template-resolver.ts +0 -185
  121. package/src/commands/start/types.ts +0 -73
  122. package/src/commands/start.test.ts +0 -480
  123. package/src/commands/status.test.ts +0 -20
  124. package/src/commands/status.ts +0 -470
  125. package/src/commands/stop.test.ts +0 -84
  126. package/src/commands/stop.ts +0 -169
  127. package/src/commands/templates.test.ts +0 -58
  128. package/src/commands/templates.ts +0 -277
  129. package/src/index.ts +0 -166
  130. package/src/lib/admin-auth.ts +0 -157
  131. package/src/lib/admin-client.test.ts +0 -51
  132. package/src/lib/admin-client.ts +0 -326
  133. package/src/lib/admin-platform-routes.ts +0 -1017
  134. package/src/lib/admin-platform-stream.test.ts +0 -434
  135. package/src/lib/admin-platform-stream.ts +0 -788
  136. package/src/lib/admin-responses.ts +0 -211
  137. package/src/lib/admin-server.test.ts +0 -781
  138. package/src/lib/admin-server.ts +0 -797
  139. package/src/lib/auth-store.ts +0 -87
  140. package/src/lib/daemon-registry.test.ts +0 -429
  141. package/src/lib/daemon-registry.ts +0 -343
  142. package/src/lib/le/le-apply.test.ts +0 -591
  143. package/src/lib/le/le-apply.ts +0 -832
  144. package/src/lib/le/le-auth.ts +0 -89
  145. package/src/lib/le/le-cleanup.test.ts +0 -168
  146. package/src/lib/le/le-cleanup.ts +0 -399
  147. package/src/lib/le/le-cli-helpers.ts +0 -77
  148. package/src/lib/le/le-client.ts +0 -184
  149. package/src/lib/le/le-concurrency.test.ts +0 -157
  150. package/src/lib/le/le-concurrency.ts +0 -78
  151. package/src/lib/le/le-connection-test.ts +0 -42
  152. package/src/lib/le/le-devicehub-properties.test.ts +0 -53
  153. package/src/lib/le/le-devicehub-properties.ts +0 -33
  154. package/src/lib/le/le-devicehub.ts +0 -213
  155. package/src/lib/le/le-digital-twins.ts +0 -285
  156. package/src/lib/le/le-drivers.ts +0 -23
  157. package/src/lib/le/le-endpoint.ts +0 -40
  158. package/src/lib/le/le-environments.test.ts +0 -73
  159. package/src/lib/le/le-environments.ts +0 -156
  160. package/src/lib/le/le-markers.ts +0 -87
  161. package/src/lib/le/le-plan.test.ts +0 -26
  162. package/src/lib/le/le-plan.ts +0 -109
  163. package/src/lib/le/le-platform-operations.test.ts +0 -198
  164. package/src/lib/le/le-platform-operations.ts +0 -340
  165. package/src/lib/le/le-static-model-schema.ts +0 -78
  166. package/src/lib/le/le-static-planner.test.ts +0 -255
  167. package/src/lib/le/le-static-planner.ts +0 -442
  168. package/src/lib/le/le-template-exporter.test.ts +0 -527
  169. package/src/lib/le/le-template-exporter.ts +0 -459
  170. package/src/lib/le/le-value-types.ts +0 -23
  171. package/src/lib/mqtt-client.ts +0 -115
  172. package/src/lib/parse.test.ts +0 -123
  173. package/src/lib/parse.ts +0 -87
  174. package/src/lib/resolve-simulation.test.ts +0 -118
  175. package/src/lib/resolve-simulation.ts +0 -181
  176. package/src/lib/session-store.ts +0 -110
  177. package/src/lib/shutdown.test.ts +0 -562
  178. package/src/lib/shutdown.ts +0 -621
  179. package/src/lib/template-category.test.ts +0 -71
  180. package/src/lib/template-category.ts +0 -72
  181. package/src/lib/template-locator.ts +0 -55
  182. package/src/lib/template-utils.ts +0 -45
  183. package/src/lib/ws-hub.ts +0 -281
  184. package/src/logger.test.ts +0 -172
  185. package/src/logger.ts +0 -402
  186. package/src/output.test.ts +0 -64
  187. 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
- });