mindsim 0.1.5 → 0.1.7
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/README.md +110 -2
- package/dist/cli.js +96 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +58 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +357 -13
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +60 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -4
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +63 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +98 -0
- package/dist/logger.js.map +1 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +37 -6
- package/dist/version.js.map +1 -1
- package/package.json +2 -1
- package/src/cli.ts +106 -0
- package/src/config.ts +409 -17
- package/src/index.ts +177 -5
- package/src/logger.ts +111 -0
- package/src/types.ts +59 -0
- package/src/version.ts +45 -6
- package/tests/config.test.ts +465 -1
- package/tests/service-json.test.ts +567 -0
- package/tests/version.test.ts +63 -4
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import * as configModule from "../src/config";
|
|
3
|
+
import { isBrowserEnvironment, isNodeEnvironment, MindSim } from "../src/index";
|
|
4
|
+
import type { MindsimServiceJson } from "../src/types";
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Test Data
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
const VALID_SERVICE_JSON: MindsimServiceJson = {
|
|
11
|
+
type: "mindsim_service_account",
|
|
12
|
+
version: "1",
|
|
13
|
+
project_id: "test-app",
|
|
14
|
+
project_name: "Test App",
|
|
15
|
+
environment: "production",
|
|
16
|
+
client_email: "test-app@mindsim.reasoner.com",
|
|
17
|
+
client_id: "550e8400-e29b-41d4-a716-446655440000",
|
|
18
|
+
api_key: "ms_prod_testkey123456789",
|
|
19
|
+
api_base_url: "https://api.reasoner.com/api/mindsim",
|
|
20
|
+
scopes: ["minds:read", "simulate:run"],
|
|
21
|
+
app_metadata: {
|
|
22
|
+
app_id: "550e8400-e29b-41d4-a716-446655440001",
|
|
23
|
+
workspace_id: "550e8400-e29b-41d4-a716-446655440002",
|
|
24
|
+
mindsim_org_id: "550e8400-e29b-41d4-a716-446655440003",
|
|
25
|
+
use_case: "internal-workflow",
|
|
26
|
+
},
|
|
27
|
+
created_at: "2025-12-21T00:00:00.000Z",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Mock Setup
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
// Mock the config module to control loadApiKey and loadServiceJson
|
|
35
|
+
vi.mock("../src/config", async (importOriginal) => {
|
|
36
|
+
const actual = await importOriginal<typeof configModule>();
|
|
37
|
+
return {
|
|
38
|
+
...actual,
|
|
39
|
+
loadApiKey: vi.fn(),
|
|
40
|
+
loadServiceJson: vi.fn(),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Mock version check to prevent network calls
|
|
45
|
+
vi.mock("../src/version", () => ({
|
|
46
|
+
checkForUpdates: vi.fn().mockResolvedValue(undefined),
|
|
47
|
+
getPackageVersion: vi.fn().mockReturnValue("1.0.0-test"),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Environment Detection Tests
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
describe("Environment Detection", () => {
|
|
55
|
+
describe("isNodeEnvironment()", () => {
|
|
56
|
+
it("should return true in Node.js environment", () => {
|
|
57
|
+
// In the test environment (Node.js), this should be true
|
|
58
|
+
expect(isNodeEnvironment()).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should check for process.versions.node", () => {
|
|
62
|
+
// Verify the function checks the right property
|
|
63
|
+
expect(typeof process.versions.node).toBe("string");
|
|
64
|
+
expect(isNodeEnvironment()).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("isBrowserEnvironment()", () => {
|
|
69
|
+
it("should return false in Node.js environment", () => {
|
|
70
|
+
// In Node.js test environment, there's no window/document
|
|
71
|
+
expect(isBrowserEnvironment()).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// MindSim Constructor with Service JSON Tests
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
80
|
+
describe("MindSim Constructor with Service JSON", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
vi.resetModules();
|
|
83
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue(null);
|
|
84
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(null);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
vi.unstubAllEnvs();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("serviceJson via constructor options", () => {
|
|
92
|
+
it("should accept valid serviceJson passed via options", () => {
|
|
93
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
94
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
95
|
+
expect(mindsim.getServiceJson()).toEqual(VALID_SERVICE_JSON);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should use api_key from serviceJson", () => {
|
|
99
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
100
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
101
|
+
// The constructor should have used the api_key from service JSON
|
|
102
|
+
expect(mindsim.getAuthMethod()).toBe("service_json");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should set authMethod to service_json", () => {
|
|
106
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
107
|
+
expect(mindsim.getAuthMethod()).toBe("service_json");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should throw on invalid serviceJson", () => {
|
|
111
|
+
const invalidServiceJson = {
|
|
112
|
+
...VALID_SERVICE_JSON,
|
|
113
|
+
type: "invalid_type" as const, // Invalid type
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
expect(() => {
|
|
117
|
+
new MindSim(undefined, { serviceJson: invalidServiceJson as MindsimServiceJson });
|
|
118
|
+
}).toThrow("Invalid serviceJson");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should throw with detailed validation errors", () => {
|
|
122
|
+
const invalidServiceJson = {
|
|
123
|
+
...VALID_SERVICE_JSON,
|
|
124
|
+
client_id: "not-a-uuid", // Invalid UUID
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
expect(() => {
|
|
128
|
+
new MindSim(undefined, { serviceJson: invalidServiceJson as MindsimServiceJson });
|
|
129
|
+
}).toThrow("client_id");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should validate email format in serviceJson", () => {
|
|
133
|
+
const invalidServiceJson = {
|
|
134
|
+
...VALID_SERVICE_JSON,
|
|
135
|
+
client_email: "invalid-email", // Missing @ and domain
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
expect(() => {
|
|
139
|
+
new MindSim(undefined, { serviceJson: invalidServiceJson as MindsimServiceJson });
|
|
140
|
+
}).toThrow("client_email");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should validate api_base_url as HTTPS", () => {
|
|
144
|
+
const invalidServiceJson = {
|
|
145
|
+
...VALID_SERVICE_JSON,
|
|
146
|
+
api_base_url: "http://insecure.com", // HTTP instead of HTTPS
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
expect(() => {
|
|
150
|
+
new MindSim(undefined, { serviceJson: invalidServiceJson as MindsimServiceJson });
|
|
151
|
+
}).toThrow("api_base_url");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should validate scopes array", () => {
|
|
155
|
+
const invalidServiceJson = {
|
|
156
|
+
...VALID_SERVICE_JSON,
|
|
157
|
+
scopes: ["minds:read", "invalid:scope"] as any[], // Invalid scope
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
expect(() => {
|
|
161
|
+
new MindSim(undefined, { serviceJson: invalidServiceJson as MindsimServiceJson });
|
|
162
|
+
}).toThrow("scopes");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should use api_base_url from serviceJson", () => {
|
|
166
|
+
const customServiceJson = {
|
|
167
|
+
...VALID_SERVICE_JSON,
|
|
168
|
+
api_base_url: "https://custom.api.example.com/v1",
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const mindsim = new MindSim(undefined, { serviceJson: customServiceJson });
|
|
172
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
173
|
+
// The client should be configured with the custom base URL
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("serviceJson via environment (loadServiceJson)", () => {
|
|
178
|
+
it("should load serviceJson from environment when no constructor args", () => {
|
|
179
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(VALID_SERVICE_JSON);
|
|
180
|
+
|
|
181
|
+
const mindsim = new MindSim();
|
|
182
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
183
|
+
expect(mindsim.getServiceJson()).toEqual(VALID_SERVICE_JSON);
|
|
184
|
+
expect(mindsim.getAuthMethod()).toBe("service_json");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should use api_key from loaded serviceJson", () => {
|
|
188
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(VALID_SERVICE_JSON);
|
|
189
|
+
|
|
190
|
+
const mindsim = new MindSim();
|
|
191
|
+
expect(mindsim.isUsingServiceJsonAuth()).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("credential priority", () => {
|
|
196
|
+
it("should prioritize constructor apiKey over serviceJson option", () => {
|
|
197
|
+
const mindsim = new MindSim("direct-api-key", { serviceJson: VALID_SERVICE_JSON });
|
|
198
|
+
|
|
199
|
+
expect(mindsim.getAuthMethod()).toBe("api_key_constructor");
|
|
200
|
+
expect(mindsim.getServiceJson()).toBeNull();
|
|
201
|
+
expect(mindsim.isUsingServiceJsonAuth()).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should prioritize constructor apiKey over environment serviceJson", () => {
|
|
205
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(VALID_SERVICE_JSON);
|
|
206
|
+
|
|
207
|
+
const mindsim = new MindSim("direct-api-key");
|
|
208
|
+
|
|
209
|
+
expect(mindsim.getAuthMethod()).toBe("api_key_constructor");
|
|
210
|
+
expect(mindsim.isUsingServiceJsonAuth()).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should prioritize constructor serviceJson over environment serviceJson", () => {
|
|
214
|
+
const envServiceJson = {
|
|
215
|
+
...VALID_SERVICE_JSON,
|
|
216
|
+
project_id: "env-app",
|
|
217
|
+
api_key: "ms_prod_env_key",
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const constructorServiceJson = {
|
|
221
|
+
...VALID_SERVICE_JSON,
|
|
222
|
+
project_id: "constructor-app",
|
|
223
|
+
api_key: "ms_prod_constructor_key",
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(envServiceJson);
|
|
227
|
+
|
|
228
|
+
const mindsim = new MindSim(undefined, { serviceJson: constructorServiceJson });
|
|
229
|
+
|
|
230
|
+
expect(mindsim.getServiceJson()?.project_id).toBe("constructor-app");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should fallback to loadApiKey if no serviceJson", () => {
|
|
234
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue("env-api-key");
|
|
235
|
+
|
|
236
|
+
const mindsim = new MindSim();
|
|
237
|
+
|
|
238
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
239
|
+
expect(mindsim.isUsingServiceJsonAuth()).toBe(false);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("custom apiBaseUrl option", () => {
|
|
244
|
+
it("should override serviceJson api_base_url when explicitly provided", () => {
|
|
245
|
+
const mindsim = new MindSim(undefined, {
|
|
246
|
+
serviceJson: VALID_SERVICE_JSON,
|
|
247
|
+
apiBaseUrl: "https://override.api.com/v2",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
251
|
+
// The apiBaseUrl option should take precedence over serviceJson.api_base_url
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should use serviceJson api_base_url when no explicit override", () => {
|
|
255
|
+
const mindsim = new MindSim(undefined, {
|
|
256
|
+
serviceJson: VALID_SERVICE_JSON,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
260
|
+
// Should use VALID_SERVICE_JSON.api_base_url
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// =============================================================================
|
|
266
|
+
// Auth Method Detection Tests
|
|
267
|
+
// =============================================================================
|
|
268
|
+
|
|
269
|
+
describe("Auth Method Detection", () => {
|
|
270
|
+
beforeEach(() => {
|
|
271
|
+
vi.resetModules();
|
|
272
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue(null);
|
|
273
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(null);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
afterEach(() => {
|
|
277
|
+
vi.unstubAllEnvs();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("getAuthMethod()", () => {
|
|
281
|
+
it("should return api_key_constructor when apiKey passed to constructor", () => {
|
|
282
|
+
const mindsim = new MindSim("test-key");
|
|
283
|
+
expect(mindsim.getAuthMethod()).toBe("api_key_constructor");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should return service_json when using serviceJson", () => {
|
|
287
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
288
|
+
expect(mindsim.getAuthMethod()).toBe("service_json");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("should return api_key_env when MINDSIM_API_KEY is set", () => {
|
|
292
|
+
vi.stubEnv("MINDSIM_API_KEY", "env-key");
|
|
293
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue("env-key");
|
|
294
|
+
|
|
295
|
+
const mindsim = new MindSim();
|
|
296
|
+
expect(mindsim.getAuthMethod()).toBe("api_key_env");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("should return config_file when using config file", () => {
|
|
300
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue("config-file-key");
|
|
301
|
+
|
|
302
|
+
const mindsim = new MindSim();
|
|
303
|
+
expect(mindsim.getAuthMethod()).toBe("config_file");
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("isUsingServiceJsonAuth()", () => {
|
|
308
|
+
it("should return true when using serviceJson", () => {
|
|
309
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
310
|
+
expect(mindsim.isUsingServiceJsonAuth()).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("should return false when using direct apiKey", () => {
|
|
314
|
+
const mindsim = new MindSim("test-key");
|
|
315
|
+
expect(mindsim.isUsingServiceJsonAuth()).toBe(false);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should return false when using env apiKey", () => {
|
|
319
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue("env-key");
|
|
320
|
+
const mindsim = new MindSim();
|
|
321
|
+
expect(mindsim.isUsingServiceJsonAuth()).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe("getAuthInfo()", () => {
|
|
326
|
+
it("should return complete auth info for serviceJson auth", () => {
|
|
327
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
328
|
+
const authInfo = mindsim.getAuthInfo();
|
|
329
|
+
|
|
330
|
+
expect(authInfo).toEqual({
|
|
331
|
+
method: "service_json",
|
|
332
|
+
isServiceJson: true,
|
|
333
|
+
projectId: VALID_SERVICE_JSON.project_id,
|
|
334
|
+
projectName: VALID_SERVICE_JSON.project_name,
|
|
335
|
+
environment: VALID_SERVICE_JSON.environment,
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("should return null project info for non-serviceJson auth", () => {
|
|
340
|
+
const mindsim = new MindSim("test-key");
|
|
341
|
+
const authInfo = mindsim.getAuthInfo();
|
|
342
|
+
|
|
343
|
+
expect(authInfo).toEqual({
|
|
344
|
+
method: "api_key_constructor",
|
|
345
|
+
isServiceJson: false,
|
|
346
|
+
projectId: null,
|
|
347
|
+
projectName: null,
|
|
348
|
+
environment: null,
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should not expose the API key", () => {
|
|
353
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
354
|
+
const authInfo = mindsim.getAuthInfo();
|
|
355
|
+
|
|
356
|
+
// Ensure no api_key in the returned object
|
|
357
|
+
expect(authInfo).not.toHaveProperty("apiKey");
|
|
358
|
+
expect(authInfo).not.toHaveProperty("api_key");
|
|
359
|
+
expect(JSON.stringify(authInfo)).not.toContain(VALID_SERVICE_JSON.api_key);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// =============================================================================
|
|
365
|
+
// Service JSON Getter Tests
|
|
366
|
+
// =============================================================================
|
|
367
|
+
|
|
368
|
+
describe("getServiceJson()", () => {
|
|
369
|
+
beforeEach(() => {
|
|
370
|
+
vi.resetModules();
|
|
371
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue(null);
|
|
372
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(null);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
afterEach(() => {
|
|
376
|
+
vi.unstubAllEnvs();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("should return the loaded serviceJson", () => {
|
|
380
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
381
|
+
expect(mindsim.getServiceJson()).toEqual(VALID_SERVICE_JSON);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it("should return null when not using serviceJson", () => {
|
|
385
|
+
const mindsim = new MindSim("test-key");
|
|
386
|
+
expect(mindsim.getServiceJson()).toBeNull();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("should return the full serviceJson object with all fields", () => {
|
|
390
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
391
|
+
const serviceJson = mindsim.getServiceJson();
|
|
392
|
+
|
|
393
|
+
expect(serviceJson).toHaveProperty("type", "mindsim_service_account");
|
|
394
|
+
expect(serviceJson).toHaveProperty("version");
|
|
395
|
+
expect(serviceJson).toHaveProperty("project_id");
|
|
396
|
+
expect(serviceJson).toHaveProperty("project_name");
|
|
397
|
+
expect(serviceJson).toHaveProperty("environment", "production");
|
|
398
|
+
expect(serviceJson).toHaveProperty("client_email");
|
|
399
|
+
expect(serviceJson).toHaveProperty("client_id");
|
|
400
|
+
expect(serviceJson).toHaveProperty("api_key");
|
|
401
|
+
expect(serviceJson).toHaveProperty("api_base_url");
|
|
402
|
+
expect(serviceJson).toHaveProperty("scopes");
|
|
403
|
+
expect(serviceJson).toHaveProperty("app_metadata");
|
|
404
|
+
expect(serviceJson).toHaveProperty("created_at");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("should return serviceJson with app_metadata containing all required fields", () => {
|
|
408
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
409
|
+
const serviceJson = mindsim.getServiceJson();
|
|
410
|
+
|
|
411
|
+
expect(serviceJson?.app_metadata).toHaveProperty("app_id");
|
|
412
|
+
expect(serviceJson?.app_metadata).toHaveProperty("workspace_id");
|
|
413
|
+
expect(serviceJson?.app_metadata).toHaveProperty("mindsim_org_id");
|
|
414
|
+
expect(serviceJson?.app_metadata).toHaveProperty("use_case");
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// =============================================================================
|
|
419
|
+
// Error Handling Tests
|
|
420
|
+
// =============================================================================
|
|
421
|
+
|
|
422
|
+
describe("Error Handling", () => {
|
|
423
|
+
beforeEach(() => {
|
|
424
|
+
vi.resetModules();
|
|
425
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue(null);
|
|
426
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(null);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
afterEach(() => {
|
|
430
|
+
vi.unstubAllEnvs();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("should throw helpful error when no credentials found", () => {
|
|
434
|
+
expect(() => {
|
|
435
|
+
new MindSim();
|
|
436
|
+
}).toThrow("API Key not found");
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("should mention serviceJson in error message", () => {
|
|
440
|
+
expect(() => {
|
|
441
|
+
new MindSim();
|
|
442
|
+
}).toThrow(/MINDSIM_SERVICE_JSON/);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("should throw for serviceJson with missing required fields", () => {
|
|
446
|
+
const incompleteServiceJson = {
|
|
447
|
+
type: "mindsim_service_account" as const,
|
|
448
|
+
// Missing most required fields
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
expect(() => {
|
|
452
|
+
new MindSim(undefined, { serviceJson: incompleteServiceJson as MindsimServiceJson });
|
|
453
|
+
}).toThrow("Invalid serviceJson");
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("should throw for serviceJson with wrong type", () => {
|
|
457
|
+
const wrongTypeServiceJson = {
|
|
458
|
+
...VALID_SERVICE_JSON,
|
|
459
|
+
type: "google_service_account" as const, // Wrong type
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
expect(() => {
|
|
463
|
+
new MindSim(undefined, { serviceJson: wrongTypeServiceJson as MindsimServiceJson });
|
|
464
|
+
}).toThrow();
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// =============================================================================
|
|
469
|
+
// Optional Fields Tests
|
|
470
|
+
// =============================================================================
|
|
471
|
+
|
|
472
|
+
describe("Optional Service JSON Fields", () => {
|
|
473
|
+
beforeEach(() => {
|
|
474
|
+
vi.resetModules();
|
|
475
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue(null);
|
|
476
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(null);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
afterEach(() => {
|
|
480
|
+
vi.unstubAllEnvs();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("should accept serviceJson with expires_at", () => {
|
|
484
|
+
const serviceJsonWithExpiry = {
|
|
485
|
+
...VALID_SERVICE_JSON,
|
|
486
|
+
expires_at: "2026-12-21T00:00:00.000Z",
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const mindsim = new MindSim(undefined, { serviceJson: serviceJsonWithExpiry });
|
|
490
|
+
expect(mindsim.getServiceJson()?.expires_at).toBe("2026-12-21T00:00:00.000Z");
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("should accept serviceJson with issued_at", () => {
|
|
494
|
+
const serviceJsonWithIssuedAt = {
|
|
495
|
+
...VALID_SERVICE_JSON,
|
|
496
|
+
issued_at: "2025-12-21T00:00:00.000Z",
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const mindsim = new MindSim(undefined, { serviceJson: serviceJsonWithIssuedAt });
|
|
500
|
+
expect(mindsim.getServiceJson()?.issued_at).toBe("2025-12-21T00:00:00.000Z");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("should accept serviceJson without optional fields", () => {
|
|
504
|
+
// VALID_SERVICE_JSON doesn't have expires_at or issued_at
|
|
505
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
506
|
+
expect(mindsim.getServiceJson()?.expires_at).toBeUndefined();
|
|
507
|
+
expect(mindsim.getServiceJson()?.issued_at).toBeUndefined();
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// =============================================================================
|
|
512
|
+
// Type Safety Tests
|
|
513
|
+
// =============================================================================
|
|
514
|
+
|
|
515
|
+
describe("Type Safety", () => {
|
|
516
|
+
beforeEach(() => {
|
|
517
|
+
vi.resetModules();
|
|
518
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue(null);
|
|
519
|
+
vi.mocked(configModule.loadServiceJson).mockReturnValue(null);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
afterEach(() => {
|
|
523
|
+
vi.unstubAllEnvs();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it("should accept all valid scopes", () => {
|
|
527
|
+
const allScopes: MindsimServiceJson["scopes"] = [
|
|
528
|
+
"minds:read",
|
|
529
|
+
"minds:write",
|
|
530
|
+
"simulate:run",
|
|
531
|
+
"simulate:read",
|
|
532
|
+
"users:read",
|
|
533
|
+
"users:write",
|
|
534
|
+
"org:admin",
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
const serviceJsonWithAllScopes = {
|
|
538
|
+
...VALID_SERVICE_JSON,
|
|
539
|
+
scopes: allScopes,
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const mindsim = new MindSim(undefined, { serviceJson: serviceJsonWithAllScopes });
|
|
543
|
+
expect(mindsim.getServiceJson()?.scopes).toEqual(allScopes);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it("should accept all valid use_case values", () => {
|
|
547
|
+
const useCases = ["internal-workflow", "customer-facing", "agentic-ai"] as const;
|
|
548
|
+
|
|
549
|
+
for (const useCase of useCases) {
|
|
550
|
+
const serviceJson = {
|
|
551
|
+
...VALID_SERVICE_JSON,
|
|
552
|
+
app_metadata: {
|
|
553
|
+
...VALID_SERVICE_JSON.app_metadata,
|
|
554
|
+
use_case: useCase,
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const mindsim = new MindSim(undefined, { serviceJson });
|
|
559
|
+
expect(mindsim.getServiceJson()?.app_metadata.use_case).toBe(useCase);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("should enforce environment as production", () => {
|
|
564
|
+
const mindsim = new MindSim(undefined, { serviceJson: VALID_SERVICE_JSON });
|
|
565
|
+
expect(mindsim.getServiceJson()?.environment).toBe("production");
|
|
566
|
+
});
|
|
567
|
+
});
|
package/tests/version.test.ts
CHANGED
|
@@ -165,17 +165,27 @@ describe("Version Module", () => {
|
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
describe("updateSdk", () => {
|
|
168
|
+
const MOCK_CWD = "/mock/project";
|
|
169
|
+
|
|
168
170
|
beforeEach(() => {
|
|
169
171
|
// Default local version
|
|
170
172
|
mockedFs.existsSync.mockReturnValue(true);
|
|
171
173
|
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.0" }));
|
|
174
|
+
// Mock process.cwd for package manager detection
|
|
175
|
+
vi.spyOn(process, "cwd").mockReturnValue(MOCK_CWD);
|
|
176
|
+
// Mock path.join to return predictable paths for lockfile checks
|
|
177
|
+
mockedPath.join.mockImplementation((...args) => args.join("/"));
|
|
172
178
|
});
|
|
173
179
|
|
|
174
|
-
it("should
|
|
180
|
+
it("should use npm install when no lockfile is found", async () => {
|
|
181
|
+
// No lockfiles found - defaults to npm
|
|
182
|
+
mockedFs.existsSync.mockImplementation((p) => {
|
|
183
|
+
if (p === MOCK_PACKAGE_PATH) return true; // package.json exists
|
|
184
|
+
return false; // no lockfiles
|
|
185
|
+
});
|
|
175
186
|
mockedAxios.get.mockResolvedValue({
|
|
176
187
|
data: { "dist-tags": { latest: "2.0.0" } },
|
|
177
188
|
});
|
|
178
|
-
// Mock the execAsync resolution
|
|
179
189
|
mockExecAsync.mockResolvedValue({ stdout: "ok", stderr: "" });
|
|
180
190
|
|
|
181
191
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
@@ -186,7 +196,49 @@ describe("Version Module", () => {
|
|
|
186
196
|
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Update complete"));
|
|
187
197
|
});
|
|
188
198
|
|
|
199
|
+
it("should use pnpm add when pnpm-lock.yaml is found", async () => {
|
|
200
|
+
mockedFs.existsSync.mockImplementation((p) => {
|
|
201
|
+
if (p === MOCK_PACKAGE_PATH) return true;
|
|
202
|
+
if (p === `${MOCK_CWD}/pnpm-lock.yaml`) return true;
|
|
203
|
+
return false;
|
|
204
|
+
});
|
|
205
|
+
mockedAxios.get.mockResolvedValue({
|
|
206
|
+
data: { "dist-tags": { latest: "2.0.0" } },
|
|
207
|
+
});
|
|
208
|
+
mockExecAsync.mockResolvedValue({ stdout: "ok", stderr: "" });
|
|
209
|
+
|
|
210
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
211
|
+
|
|
212
|
+
await updateSdk();
|
|
213
|
+
|
|
214
|
+
expect(mockExecAsync).toHaveBeenCalledWith("pnpm add mindsim@latest");
|
|
215
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Update complete"));
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should use yarn add when yarn.lock is found", async () => {
|
|
219
|
+
mockedFs.existsSync.mockImplementation((p) => {
|
|
220
|
+
if (p === MOCK_PACKAGE_PATH) return true;
|
|
221
|
+
if (p === `${MOCK_CWD}/yarn.lock`) return true;
|
|
222
|
+
return false;
|
|
223
|
+
});
|
|
224
|
+
mockedAxios.get.mockResolvedValue({
|
|
225
|
+
data: { "dist-tags": { latest: "2.0.0" } },
|
|
226
|
+
});
|
|
227
|
+
mockExecAsync.mockResolvedValue({ stdout: "ok", stderr: "" });
|
|
228
|
+
|
|
229
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
230
|
+
|
|
231
|
+
await updateSdk();
|
|
232
|
+
|
|
233
|
+
expect(mockExecAsync).toHaveBeenCalledWith("yarn add mindsim@latest");
|
|
234
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Update complete"));
|
|
235
|
+
});
|
|
236
|
+
|
|
189
237
|
it("should do nothing if already on latest", async () => {
|
|
238
|
+
mockedFs.existsSync.mockImplementation((p) => {
|
|
239
|
+
if (p === MOCK_PACKAGE_PATH) return true;
|
|
240
|
+
return false;
|
|
241
|
+
});
|
|
190
242
|
mockedAxios.get.mockResolvedValue({
|
|
191
243
|
data: { "dist-tags": { latest: "1.0.0" } },
|
|
192
244
|
});
|
|
@@ -201,11 +253,15 @@ describe("Version Module", () => {
|
|
|
201
253
|
);
|
|
202
254
|
});
|
|
203
255
|
|
|
204
|
-
it("should handle exec errors", async () => {
|
|
256
|
+
it("should handle exec errors and show correct manual command", async () => {
|
|
257
|
+
mockedFs.existsSync.mockImplementation((p) => {
|
|
258
|
+
if (p === MOCK_PACKAGE_PATH) return true;
|
|
259
|
+
if (p === `${MOCK_CWD}/pnpm-lock.yaml`) return true;
|
|
260
|
+
return false;
|
|
261
|
+
});
|
|
205
262
|
mockedAxios.get.mockResolvedValue({
|
|
206
263
|
data: { "dist-tags": { latest: "2.0.0" } },
|
|
207
264
|
});
|
|
208
|
-
// Mock execAsync rejection
|
|
209
265
|
mockExecAsync.mockRejectedValue(new Error("Permission denied"));
|
|
210
266
|
|
|
211
267
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
@@ -214,6 +270,9 @@ describe("Version Module", () => {
|
|
|
214
270
|
|
|
215
271
|
expect(consoleErrorSpy).toHaveBeenCalledWith("❌ Failed to update MindSim SDK.");
|
|
216
272
|
expect(consoleErrorSpy).toHaveBeenCalledWith("Permission denied");
|
|
273
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
274
|
+
"Please try running manually: pnpm add mindsim@latest",
|
|
275
|
+
);
|
|
217
276
|
});
|
|
218
277
|
});
|
|
219
278
|
});
|