mindsim 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +32 -0
- package/.github/workflows/test.yml +28 -0
- package/.github/workflows/type-checks.yml +29 -0
- package/LICENSE +21 -0
- package/README.md +748 -0
- package/assets/mindsim-logo.svg +15 -0
- package/biome.jsonc +43 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +115 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +36 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +63 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/artifacts.d.ts +8 -0
- package/dist/resources/artifacts.d.ts.map +1 -0
- package/dist/resources/artifacts.js +17 -0
- package/dist/resources/artifacts.js.map +1 -0
- package/dist/resources/minds.d.ts +39 -0
- package/dist/resources/minds.d.ts.map +1 -0
- package/dist/resources/minds.js +85 -0
- package/dist/resources/minds.js.map +1 -0
- package/dist/resources/psychometrics.d.ts +11 -0
- package/dist/resources/psychometrics.d.ts.map +1 -0
- package/dist/resources/psychometrics.js +18 -0
- package/dist/resources/psychometrics.js.map +1 -0
- package/dist/resources/simulations.d.ts +26 -0
- package/dist/resources/simulations.d.ts.map +1 -0
- package/dist/resources/simulations.js +41 -0
- package/dist/resources/simulations.js.map +1 -0
- package/dist/resources/snapshots.d.ts +27 -0
- package/dist/resources/snapshots.d.ts.map +1 -0
- package/dist/resources/snapshots.js +101 -0
- package/dist/resources/snapshots.js.map +1 -0
- package/dist/resources/tags.d.ts +23 -0
- package/dist/resources/tags.d.ts.map +1 -0
- package/dist/resources/tags.js +32 -0
- package/dist/resources/tags.js.map +1 -0
- package/dist/types.d.ts +161 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +15 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +105 -0
- package/dist/version.js.map +1 -0
- package/package.json +55 -0
- package/src/auth.ts +131 -0
- package/src/cli.ts +41 -0
- package/src/config.ts +60 -0
- package/src/index.ts +59 -0
- package/src/resources/artifacts.ts +13 -0
- package/src/resources/minds.ts +98 -0
- package/src/resources/psychometrics.ts +16 -0
- package/src/resources/simulations.ts +49 -0
- package/src/resources/snapshots.ts +126 -0
- package/src/resources/tags.ts +30 -0
- package/src/types.ts +185 -0
- package/src/version.ts +111 -0
- package/tests/auth.test.ts +41 -0
- package/tests/config.test.ts +129 -0
- package/tests/resources/minds.test.ts +119 -0
- package/tests/resources/psychometrics.test.ts +38 -0
- package/tests/resources/simulation.test.ts +94 -0
- package/tests/resources/snapshots.test.ts +135 -0
- package/tests/resources/tags.test.ts +87 -0
- package/tests/use-cases/quickstart.test.ts +84 -0
- package/tests/version.test.ts +221 -0
- package/tsconfig.json +29 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
3
|
+
import { MindSim } from "../../src/index";
|
|
4
|
+
|
|
5
|
+
vi.mock("axios");
|
|
6
|
+
const mockedAxios = axios as Mocked<typeof axios>;
|
|
7
|
+
|
|
8
|
+
describe("MindSim Simulations Resource", () => {
|
|
9
|
+
let mindsim: MindSim;
|
|
10
|
+
const MIND_ID = "368fcac9-27d8-4cc1-b479-5b9601110086";
|
|
11
|
+
const SIM_ID = "123e4567-e89b-12d3-a456-426614174000";
|
|
12
|
+
|
|
13
|
+
const mockApiClient = {
|
|
14
|
+
get: vi.fn(),
|
|
15
|
+
post: vi.fn(),
|
|
16
|
+
delete: vi.fn(),
|
|
17
|
+
defaults: { headers: { common: {} } },
|
|
18
|
+
interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
mockedAxios.create.mockReturnValue(mockApiClient as any);
|
|
23
|
+
mindsim = new MindSim("fake-key");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("run() should post payload to simulations endpoint", async () => {
|
|
27
|
+
const mockResponse = { message: "I would use Python." };
|
|
28
|
+
mockApiClient.post.mockResolvedValue({ data: mockResponse });
|
|
29
|
+
|
|
30
|
+
const payload = {
|
|
31
|
+
mindId: MIND_ID,
|
|
32
|
+
scenario: { message: "Hello" },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const result = await mindsim.simulations.run(payload);
|
|
36
|
+
|
|
37
|
+
expect(mockApiClient.post).toHaveBeenCalledWith(expect.stringContaining("/simulate"), payload);
|
|
38
|
+
expect(result.message).toBe(mockResponse.message);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("run() with background flag", async () => {
|
|
42
|
+
mockApiClient.post.mockResolvedValue({
|
|
43
|
+
data: { message: "Queued" },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const payload = {
|
|
47
|
+
mindId: MIND_ID,
|
|
48
|
+
scenario: { message: "Analyze this" },
|
|
49
|
+
runInBackground: true,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = await mindsim.simulations.run(payload);
|
|
53
|
+
|
|
54
|
+
expect(mockApiClient.post).toHaveBeenCalledWith(
|
|
55
|
+
expect.stringContaining("/simulate"),
|
|
56
|
+
expect.objectContaining({ runInBackground: true }),
|
|
57
|
+
);
|
|
58
|
+
expect(result.message).toBe("Queued");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("list() should get simulations with pagination params", async () => {
|
|
62
|
+
mockApiClient.get.mockResolvedValue({
|
|
63
|
+
data: { simulations: [], count: 0 },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await mindsim.simulations.list({ offset: 10, limit: 5 });
|
|
67
|
+
|
|
68
|
+
expect(mockApiClient.get).toHaveBeenCalledWith("/simulations", {
|
|
69
|
+
params: { offset: 10, limit: 5 },
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("get() should retrieve specific simulation details", async () => {
|
|
74
|
+
mockApiClient.get.mockResolvedValue({
|
|
75
|
+
data: { simulation: { id: SIM_ID }, messages: [] },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const result = await mindsim.simulations.get(SIM_ID);
|
|
79
|
+
|
|
80
|
+
expect(mockApiClient.get).toHaveBeenCalledWith(`/simulations/${SIM_ID}`);
|
|
81
|
+
expect(result.simulation.id).toBe(SIM_ID);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("delete() should remove simulation", async () => {
|
|
85
|
+
mockApiClient.delete.mockResolvedValue({
|
|
86
|
+
data: { message: "Deleted", simulationId: SIM_ID },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const result = await mindsim.simulations.delete(SIM_ID);
|
|
90
|
+
|
|
91
|
+
expect(mockApiClient.delete).toHaveBeenCalledWith(`/simulations/${SIM_ID}`);
|
|
92
|
+
expect(result.simulationId).toBe(SIM_ID);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import FormData from "form-data";
|
|
3
|
+
import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
4
|
+
import { MindSim } from "../../src/index";
|
|
5
|
+
|
|
6
|
+
vi.mock("axios");
|
|
7
|
+
const mockedAxios = axios as Mocked<typeof axios>;
|
|
8
|
+
|
|
9
|
+
describe("MindSim Snapshots Resource", () => {
|
|
10
|
+
let mindsim: MindSim;
|
|
11
|
+
|
|
12
|
+
const MIND_ID = "ac8ac8c4-05e9-4046-a919-4cbae2f49f22";
|
|
13
|
+
const ARTIFACT_ID = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
|
|
14
|
+
const ASSESSMENT_ID = "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11";
|
|
15
|
+
|
|
16
|
+
const mockApiClient = {
|
|
17
|
+
get: vi.fn(),
|
|
18
|
+
post: vi.fn(),
|
|
19
|
+
put: vi.fn(),
|
|
20
|
+
defaults: { headers: { common: {} } },
|
|
21
|
+
interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
mockedAxios.create.mockReturnValue(mockApiClient as any);
|
|
26
|
+
mindsim = new MindSim("fake-key");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("list() should retrieve snapshots for a mind", async () => {
|
|
30
|
+
const mockList = {
|
|
31
|
+
snapshots: [{ id: ASSESSMENT_ID, status: "completed" }],
|
|
32
|
+
count: 1,
|
|
33
|
+
};
|
|
34
|
+
mockApiClient.get.mockResolvedValue({ data: mockList });
|
|
35
|
+
|
|
36
|
+
const result = await mindsim.snapshots.list(MIND_ID);
|
|
37
|
+
|
|
38
|
+
expect(mockApiClient.get).toHaveBeenCalledWith(`/minds/${MIND_ID}/snapshots`);
|
|
39
|
+
expect(result.count).toBe(1);
|
|
40
|
+
expect(result.snapshots[0].id).toBe(ASSESSMENT_ID);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("create() should execute the 3-step signed URL flow", async () => {
|
|
44
|
+
const fileBuffer = Buffer.from("fake-pdf-content");
|
|
45
|
+
const fileName = "test.pdf";
|
|
46
|
+
|
|
47
|
+
// 1. Mock Get Signed URL response
|
|
48
|
+
mockApiClient.get.mockResolvedValueOnce({
|
|
49
|
+
data: {
|
|
50
|
+
signedUrl: "https://s3.fake/upload",
|
|
51
|
+
artifactId: ARTIFACT_ID,
|
|
52
|
+
fileName: "test.pdf",
|
|
53
|
+
contentType: "application/pdf",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 2. Mock the direct axios PUT to the signed URL
|
|
58
|
+
mockedAxios.put.mockResolvedValue({ status: 200 } as any);
|
|
59
|
+
|
|
60
|
+
// 3. Mock the final POST to create snapshot record
|
|
61
|
+
mockApiClient.post.mockResolvedValue({
|
|
62
|
+
data: {
|
|
63
|
+
message: "Success",
|
|
64
|
+
mindAssessmentId: ASSESSMENT_ID,
|
|
65
|
+
artifactId: ARTIFACT_ID,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = await mindsim.snapshots.create(MIND_ID, {
|
|
70
|
+
file: fileBuffer,
|
|
71
|
+
fileName: fileName,
|
|
72
|
+
contentType: "application/pdf",
|
|
73
|
+
mindsetDate: "2023-01-01",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Verifications
|
|
77
|
+
expect(mockApiClient.get).toHaveBeenCalledWith(`/minds/${MIND_ID}/signed-url`, {
|
|
78
|
+
params: { fileName, contentType: "application/pdf" },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Check global axios PUT was called for file upload
|
|
82
|
+
expect(mockedAxios.put).toHaveBeenCalledWith(
|
|
83
|
+
"https://s3.fake/upload",
|
|
84
|
+
fileBuffer,
|
|
85
|
+
expect.objectContaining({
|
|
86
|
+
headers: { "Content-Type": "application/pdf" },
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Check final POST
|
|
91
|
+
expect(mockApiClient.post).toHaveBeenCalledWith(
|
|
92
|
+
`/minds/${MIND_ID}/snapshots`,
|
|
93
|
+
expect.any(FormData),
|
|
94
|
+
expect.objectContaining({
|
|
95
|
+
headers: expect.anything(), // FormData headers
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(result.mindAssessmentId).toBe(ASSESSMENT_ID);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("createFromFile() should use FormData and return artifacts", async () => {
|
|
103
|
+
mockApiClient.post.mockResolvedValue({
|
|
104
|
+
data: { artifactId: ARTIFACT_ID },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await mindsim.snapshots.createFromFile(MIND_ID, {
|
|
108
|
+
file: Buffer.from("abc"),
|
|
109
|
+
fileName: "chat.txt",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(mockApiClient.post).toHaveBeenCalledWith(
|
|
113
|
+
`/minds/${MIND_ID}/snapshots`,
|
|
114
|
+
expect.any(FormData),
|
|
115
|
+
expect.objectContaining({ headers: expect.anything() }),
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("getStatus() should return status object with correct IDs", async () => {
|
|
120
|
+
mockApiClient.get.mockResolvedValue({
|
|
121
|
+
data: {
|
|
122
|
+
id: ASSESSMENT_ID,
|
|
123
|
+
mindId: MIND_ID,
|
|
124
|
+
status: "processing",
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const status = await mindsim.snapshots.getStatus(MIND_ID, ASSESSMENT_ID);
|
|
129
|
+
expect(status.status).toBe("processing");
|
|
130
|
+
expect(status.id).toBe(ASSESSMENT_ID);
|
|
131
|
+
expect(mockApiClient.get).toHaveBeenCalledWith(
|
|
132
|
+
`/minds/${MIND_ID}/snapshots/${ASSESSMENT_ID}/status`,
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
3
|
+
import { MindSim } from "../../src/index";
|
|
4
|
+
import type { Tag } from "../../src/types";
|
|
5
|
+
|
|
6
|
+
vi.mock("axios");
|
|
7
|
+
const mockedAxios = axios as Mocked<typeof axios>;
|
|
8
|
+
|
|
9
|
+
describe("MindSim Tags Resource", () => {
|
|
10
|
+
let mindsim: MindSim;
|
|
11
|
+
|
|
12
|
+
const TAG_ID_1 = "47a09167-3b9f-442c-a19c-96398cf238fb";
|
|
13
|
+
const TAG_ID_2 = "a1ca5e66-6f3a-4d92-ab25-8a86fdb111c1";
|
|
14
|
+
|
|
15
|
+
const mockClient = {
|
|
16
|
+
get: vi.fn(),
|
|
17
|
+
put: vi.fn(),
|
|
18
|
+
delete: vi.fn(),
|
|
19
|
+
post: vi.fn(),
|
|
20
|
+
defaults: { headers: { common: {} } },
|
|
21
|
+
interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
// setup axios.create to return our mock client
|
|
26
|
+
mockedAxios.create.mockReturnValue(mockClient as any);
|
|
27
|
+
|
|
28
|
+
mindsim = new MindSim("test-api-key");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("list", () => {
|
|
32
|
+
it("should fetch and return a list of tags via mindsim.tags.list()", async () => {
|
|
33
|
+
const mockData: Tag[] = [
|
|
34
|
+
{ id: TAG_ID_1, name: "hero", mindCount: 5 },
|
|
35
|
+
{ id: TAG_ID_2, name: "villain", mindCount: 2 },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Mock the API response
|
|
39
|
+
mockClient.get.mockResolvedValue({
|
|
40
|
+
data: { tags: mockData },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Call via the main SDK instance
|
|
44
|
+
const result = await mindsim.tags.list();
|
|
45
|
+
|
|
46
|
+
// Assertions
|
|
47
|
+
expect(mockClient.get).toHaveBeenCalledWith("/tags");
|
|
48
|
+
expect(result).toEqual(mockData);
|
|
49
|
+
expect(result).toHaveLength(2);
|
|
50
|
+
expect(result?.[0]?.id).toBe(TAG_ID_1);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("update", () => {
|
|
55
|
+
it("should update a tag and return the updated object via mindsim.tags.update()", async () => {
|
|
56
|
+
const updatePayload = { name: "superhero" };
|
|
57
|
+
const mockResponseTag: Tag = {
|
|
58
|
+
id: TAG_ID_1,
|
|
59
|
+
name: "superhero",
|
|
60
|
+
mindCount: 5,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
mockClient.put.mockResolvedValue({
|
|
64
|
+
data: { tag: mockResponseTag },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const result = await mindsim.tags.update(TAG_ID_1, updatePayload);
|
|
68
|
+
|
|
69
|
+
expect(mockClient.put).toHaveBeenCalledWith(`/tags/${TAG_ID_1}`, updatePayload);
|
|
70
|
+
expect(result).toEqual(mockResponseTag);
|
|
71
|
+
expect(result.name).toBe("superhero");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("delete", () => {
|
|
76
|
+
it("should delete a tag and return status via mindsim.tags.delete()", async () => {
|
|
77
|
+
mockClient.delete.mockResolvedValue({
|
|
78
|
+
data: { deleted: true },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const result = await mindsim.tags.delete(TAG_ID_2);
|
|
82
|
+
|
|
83
|
+
expect(mockClient.delete).toHaveBeenCalledWith(`/tags/${TAG_ID_2}`);
|
|
84
|
+
expect(result).toEqual({ deleted: true });
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
4
|
+
import { MindSim } from "../../src/index";
|
|
5
|
+
|
|
6
|
+
vi.mock("axios");
|
|
7
|
+
vi.mock("node:fs");
|
|
8
|
+
|
|
9
|
+
// Mock the version module to prevent file system access during instantiation
|
|
10
|
+
vi.mock("../../src/version", () => ({
|
|
11
|
+
getPackageVersion: () => "1.0.0",
|
|
12
|
+
checkForUpdates: vi.fn().mockResolvedValue(undefined),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const mockedAxios = axios as Mocked<typeof axios>;
|
|
16
|
+
const mockedFs = fs as Mocked<typeof fs>;
|
|
17
|
+
|
|
18
|
+
describe("Quick Start Workflow Integration", () => {
|
|
19
|
+
let mindsim: MindSim;
|
|
20
|
+
|
|
21
|
+
const BOBBY_ID = "779e640c-67c2-44e5-bc91-b3b43eeaead8";
|
|
22
|
+
const ARTIFACT_ID = "c56a4180-65aa-42ec-a945-5fd21dec0538";
|
|
23
|
+
const ASSESSMENT_ID = "550e8400-e29b-41d4-a716-446655440000";
|
|
24
|
+
|
|
25
|
+
const mockApiClient = {
|
|
26
|
+
get: vi.fn(),
|
|
27
|
+
post: vi.fn(),
|
|
28
|
+
put: vi.fn(),
|
|
29
|
+
defaults: { headers: { common: {} } },
|
|
30
|
+
interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
mockedAxios.create.mockReturnValue(mockApiClient as any);
|
|
35
|
+
mockedAxios.put.mockResolvedValue({ status: 200 } as any); // For signed URL upload
|
|
36
|
+
mindsim = new MindSim("valid-key");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should execute the full Bobby Tables workflow", async () => {
|
|
40
|
+
// --- Step 1: Create Mind ---
|
|
41
|
+
mockApiClient.post.mockResolvedValueOnce({
|
|
42
|
+
data: {
|
|
43
|
+
mind: { id: BOBBY_ID, name: "Bobby Tables", email: "bobby@test.com", tags: [] },
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const bobby = await mindsim.minds.create({
|
|
48
|
+
name: "Bobby Tables",
|
|
49
|
+
email: "bobby@test.com",
|
|
50
|
+
tags: ["developer"],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(bobby.id).toBe(BOBBY_ID);
|
|
54
|
+
|
|
55
|
+
// --- Step 2: Create Snapshot (Mocking File Read) ---
|
|
56
|
+
mockedFs.readFileSync.mockReturnValue(Buffer.from("fake-pdf"));
|
|
57
|
+
|
|
58
|
+
// Mocks for Snapshot creation flow
|
|
59
|
+
mockApiClient.get.mockResolvedValueOnce({
|
|
60
|
+
data: { signedUrl: "http://s3", artifactId: ARTIFACT_ID },
|
|
61
|
+
}); // signed-url
|
|
62
|
+
|
|
63
|
+
mockApiClient.post.mockResolvedValueOnce({
|
|
64
|
+
data: { mindAssessmentId: ASSESSMENT_ID, artifactId: ARTIFACT_ID },
|
|
65
|
+
}); // create snapshot record
|
|
66
|
+
|
|
67
|
+
const snapshotRes = await mindsim.snapshots.create(bobby.id, {
|
|
68
|
+
file: fs.readFileSync("dummy.pdf"), // Reads from mock
|
|
69
|
+
fileName: "dummy.pdf",
|
|
70
|
+
contentType: "application/pdf",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(snapshotRes.mindAssessmentId).toBe(ASSESSMENT_ID);
|
|
74
|
+
|
|
75
|
+
// --- Step 3: Check Status ---
|
|
76
|
+
mockApiClient.get.mockResolvedValueOnce({
|
|
77
|
+
data: { id: ASSESSMENT_ID, mindId: BOBBY_ID, status: "completed" },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const status = await mindsim.snapshots.getStatus(bobby.id, snapshotRes.mindAssessmentId);
|
|
81
|
+
expect(status.status).toBe("completed");
|
|
82
|
+
expect(status.mindId).toBe(BOBBY_ID);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
5
|
+
import { checkForUpdates, getPackageVersion, updateSdk } from "../src/version";
|
|
6
|
+
|
|
7
|
+
// 1. Define the mock function using vi.hoisted so it's available to the mock factory
|
|
8
|
+
const { mockExecAsync } = vi.hoisted(() => {
|
|
9
|
+
return { mockExecAsync: vi.fn() };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// 2. Mock node:util to return our hoisted mock function
|
|
13
|
+
vi.mock("node:util", async () => {
|
|
14
|
+
return {
|
|
15
|
+
promisify: () => mockExecAsync,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// 3. Mock other dependencies
|
|
20
|
+
vi.mock("node:fs");
|
|
21
|
+
vi.mock("node:path");
|
|
22
|
+
vi.mock("axios");
|
|
23
|
+
|
|
24
|
+
const mockedFs = fs as Mocked<typeof fs>;
|
|
25
|
+
const mockedPath = path as Mocked<typeof path>;
|
|
26
|
+
const mockedAxios = axios as Mocked<typeof axios>;
|
|
27
|
+
|
|
28
|
+
describe("Version Module", () => {
|
|
29
|
+
const MOCK_PACKAGE_PATH = "/mock/project/package.json";
|
|
30
|
+
const MOCK_ALT_PATH = "/mock/package.json";
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.resetModules();
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
|
|
36
|
+
// Reset the hoisted mock implementation
|
|
37
|
+
mockExecAsync.mockReset();
|
|
38
|
+
|
|
39
|
+
// Setup standard path mocks
|
|
40
|
+
mockedPath.resolve.mockReturnValue(MOCK_PACKAGE_PATH);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
vi.unstubAllEnvs();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("getPackageVersion", () => {
|
|
48
|
+
it("should return version from package.json if it exists at primary path", () => {
|
|
49
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
50
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.0" }));
|
|
51
|
+
|
|
52
|
+
const version = getPackageVersion();
|
|
53
|
+
|
|
54
|
+
expect(mockedPath.resolve).toHaveBeenCalled();
|
|
55
|
+
expect(mockedFs.readFileSync).toHaveBeenCalledWith(MOCK_PACKAGE_PATH, "utf-8");
|
|
56
|
+
expect(version).toBe("1.0.0");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should try fallback path if primary path is missing", () => {
|
|
60
|
+
// First check returns false (primary path)
|
|
61
|
+
// Second check returns true (alt path)
|
|
62
|
+
mockedFs.existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true);
|
|
63
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.1" }));
|
|
64
|
+
|
|
65
|
+
mockedPath.resolve.mockReturnValueOnce(MOCK_PACKAGE_PATH).mockReturnValueOnce(MOCK_ALT_PATH);
|
|
66
|
+
|
|
67
|
+
const version = getPackageVersion();
|
|
68
|
+
|
|
69
|
+
expect(mockedFs.readFileSync).toHaveBeenCalledWith(MOCK_ALT_PATH, "utf-8");
|
|
70
|
+
expect(version).toBe("1.0.1");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should return '0.0.0' and log warning if file read fails", () => {
|
|
74
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
75
|
+
mockedFs.readFileSync.mockImplementation(() => {
|
|
76
|
+
throw new Error("Permission denied");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
80
|
+
|
|
81
|
+
const version = getPackageVersion();
|
|
82
|
+
|
|
83
|
+
expect(version).toBe("0.0.0");
|
|
84
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
85
|
+
expect.stringContaining("Unable to determine current package version"),
|
|
86
|
+
expect.any(Error),
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should return '0.0.0' if JSON is invalid", () => {
|
|
91
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
92
|
+
mockedFs.readFileSync.mockReturnValue("INVALID_JSON");
|
|
93
|
+
|
|
94
|
+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
95
|
+
|
|
96
|
+
const version = getPackageVersion();
|
|
97
|
+
expect(version).toBe("0.0.0");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("checkForUpdates", () => {
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
// Default local version
|
|
104
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
105
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.0" }));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should log warning if remote version is greater than local", async () => {
|
|
109
|
+
mockedAxios.get.mockResolvedValue({
|
|
110
|
+
data: { "dist-tags": { latest: "1.1.0" } },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
114
|
+
|
|
115
|
+
await checkForUpdates();
|
|
116
|
+
|
|
117
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("UPDATE AVAILABLE"));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should not log if versions match", async () => {
|
|
121
|
+
mockedAxios.get.mockResolvedValue({
|
|
122
|
+
data: { "dist-tags": { latest: "1.0.0" } },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
126
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
127
|
+
|
|
128
|
+
await checkForUpdates(); // verbose defaults to false
|
|
129
|
+
|
|
130
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
131
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should log confirmation if verbose is true and versions match", async () => {
|
|
135
|
+
mockedAxios.get.mockResolvedValue({
|
|
136
|
+
data: { "dist-tags": { latest: "1.0.0" } },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
140
|
+
|
|
141
|
+
await checkForUpdates(true);
|
|
142
|
+
|
|
143
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
144
|
+
expect.stringContaining("MindSim SDK is up to date"),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should fail silently on network error (non-verbose)", async () => {
|
|
149
|
+
mockedAxios.get.mockRejectedValue(new Error("Network Error"));
|
|
150
|
+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
151
|
+
|
|
152
|
+
await checkForUpdates(false);
|
|
153
|
+
|
|
154
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should log error on failure if verbose is true", async () => {
|
|
158
|
+
mockedAxios.get.mockRejectedValue(new Error("Network Error"));
|
|
159
|
+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
160
|
+
|
|
161
|
+
await checkForUpdates(true);
|
|
162
|
+
|
|
163
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to check for updates:", "Network Error");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("updateSdk", () => {
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
// Default local version
|
|
170
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
171
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.0" }));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should run npm install if update available", async () => {
|
|
175
|
+
mockedAxios.get.mockResolvedValue({
|
|
176
|
+
data: { "dist-tags": { latest: "2.0.0" } },
|
|
177
|
+
});
|
|
178
|
+
// Mock the execAsync resolution
|
|
179
|
+
mockExecAsync.mockResolvedValue({ stdout: "ok", stderr: "" });
|
|
180
|
+
|
|
181
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
182
|
+
|
|
183
|
+
await updateSdk();
|
|
184
|
+
|
|
185
|
+
expect(mockExecAsync).toHaveBeenCalledWith(
|
|
186
|
+
"npm install @mindsim/mindsim-sdk-typescript@latest --save",
|
|
187
|
+
);
|
|
188
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Update complete"));
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should do nothing if already on latest", async () => {
|
|
192
|
+
mockedAxios.get.mockResolvedValue({
|
|
193
|
+
data: { "dist-tags": { latest: "1.0.0" } },
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
197
|
+
|
|
198
|
+
await updateSdk();
|
|
199
|
+
|
|
200
|
+
expect(mockExecAsync).not.toHaveBeenCalled();
|
|
201
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
202
|
+
expect.stringContaining("already on the latest version"),
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should handle exec errors", async () => {
|
|
207
|
+
mockedAxios.get.mockResolvedValue({
|
|
208
|
+
data: { "dist-tags": { latest: "2.0.0" } },
|
|
209
|
+
});
|
|
210
|
+
// Mock execAsync rejection
|
|
211
|
+
mockExecAsync.mockRejectedValue(new Error("Permission denied"));
|
|
212
|
+
|
|
213
|
+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
214
|
+
|
|
215
|
+
await updateSdk();
|
|
216
|
+
|
|
217
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("❌ Failed to update MindSim SDK.");
|
|
218
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("Permission denied");
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "nodenext",
|
|
4
|
+
"target": "esnext",
|
|
5
|
+
"lib": ["esnext"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"exactOptionalPropertyTypes": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"verbatimModuleSyntax": false,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"noUncheckedSideEffectImports": true,
|
|
19
|
+
"moduleDetection": "force",
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"esModuleInterop": true
|
|
22
|
+
},
|
|
23
|
+
"ts-node": {
|
|
24
|
+
"esm": true,
|
|
25
|
+
"experimentalSpecifierResolution": "node"
|
|
26
|
+
},
|
|
27
|
+
"include": ["src/**/*.ts"],
|
|
28
|
+
"exclude": ["node_modules", "tests/**/*.ts"]
|
|
29
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// vitest.config.ts
|
|
2
|
+
import { defineConfig } from "vitest/config";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true, // Allows using describe, expect, it without imports
|
|
7
|
+
environment: "node",
|
|
8
|
+
clearMocks: true,
|
|
9
|
+
include: ["tests", "**/*.test.ts", "**/*.spec.ts"],
|
|
10
|
+
exclude: ["node_modules", "dist"],
|
|
11
|
+
},
|
|
12
|
+
});
|