@malloy-publisher/server 0.0.90 → 0.0.92
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/.prettierignore +1 -0
- package/build.ts +9 -0
- package/dist/app/api-doc.yaml +3 -0
- package/dist/app/assets/{index-2BGzlUQa.js → index-DYO_URL-.js} +100 -100
- package/dist/app/assets/{index-D1X7Y0Ve.js → index-Dg-zTLb3.js} +1 -1
- package/dist/app/assets/{index.es49-D9XPPIF9.js → index.es50-CDMydA2o.js} +1 -1
- package/dist/app/assets/mui-UpaxdnvH.js +159 -0
- package/dist/app/index.html +2 -2
- package/dist/server.js +307 -98
- package/eslint.config.mjs +8 -0
- package/package.json +2 -1
- package/publisher.config.json +25 -5
- package/src/config.ts +25 -2
- package/src/constants.ts +1 -1
- package/src/controller/package.controller.ts +1 -0
- package/src/controller/watch-mode.controller.ts +19 -4
- package/src/data_styles.ts +10 -3
- package/src/mcp/prompts/index.ts +9 -1
- package/src/mcp/resources/package_resource.ts +2 -1
- package/src/mcp/resources/source_resource.ts +1 -0
- package/src/mcp/resources/view_resource.ts +1 -0
- package/src/server.ts +9 -5
- package/src/service/connection.ts +17 -4
- package/src/service/db_utils.ts +19 -11
- package/src/service/model.ts +2 -0
- package/src/service/package.spec.ts +76 -54
- package/src/service/project.ts +160 -45
- package/src/service/project_store.spec.ts +477 -165
- package/src/service/project_store.ts +319 -69
- package/src/service/scheduler.ts +3 -2
- package/src/utils.ts +0 -1
- package/tests/harness/e2e.ts +60 -58
- package/tests/harness/uris.ts +21 -24
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +10 -0
- package/dist/app/assets/mui-YektUyEU.js +0 -161
|
@@ -1,200 +1,512 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
it,
|
|
7
|
-
mock,
|
|
8
|
-
spyOn,
|
|
9
|
-
} from "bun:test";
|
|
10
|
-
import { rmSync } from "fs";
|
|
11
|
-
import * as fs from "fs/promises";
|
|
12
|
-
import path from "path";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
|
+
import { existsSync, rmSync, mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as sinon from "sinon";
|
|
5
|
+
import { components } from "../api";
|
|
13
6
|
import { isPublisherConfigFrozen } from "../config";
|
|
14
|
-
import { publisherPath } from "../constants";
|
|
15
|
-
import { FrozenConfigError, ProjectNotFoundError } from "../errors";
|
|
16
|
-
import { logger } from "../logger";
|
|
17
7
|
import { ProjectStore } from "./project_store";
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
8
|
+
import { Project } from "./project";
|
|
9
|
+
|
|
10
|
+
type Connection = components["schemas"]["Connection"];
|
|
11
|
+
|
|
12
|
+
const serverRootPath = "/tmp/pathways-worker-publisher-project-store-test";
|
|
13
|
+
const projectName = "organizationName-projectName";
|
|
14
|
+
const testConnections: Connection[] = [
|
|
15
|
+
{
|
|
16
|
+
name: "testConnection",
|
|
17
|
+
type: "postgres",
|
|
18
|
+
postgresConnection: {
|
|
19
|
+
host: "host",
|
|
20
|
+
port: 1234,
|
|
21
|
+
databaseName: "databaseName",
|
|
22
|
+
userName: "userName",
|
|
23
|
+
password: "password",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
let sandbox: sinon.SinonSandbox;
|
|
29
|
+
|
|
30
|
+
describe("ProjectStore Service", () => {
|
|
31
|
+
let projectStore: ProjectStore;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
// Clean up any existing test directory
|
|
35
|
+
if (existsSync(serverRootPath)) {
|
|
36
|
+
rmSync(serverRootPath, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
mkdirSync(serverRootPath);
|
|
39
|
+
sandbox = sinon.createSandbox();
|
|
40
|
+
|
|
41
|
+
// Mock the configuration to prevent initialization errors
|
|
42
|
+
mock(isPublisherConfigFrozen).mockReturnValue(false);
|
|
43
|
+
mock.module("../config", () => ({
|
|
44
|
+
isPublisherConfigFrozen: () => false,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
// Create project store after mocking
|
|
48
|
+
projectStore = new ProjectStore(serverRootPath);
|
|
33
49
|
});
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
// Clean up the test directory after each test
|
|
53
|
+
if (existsSync(serverRootPath)) {
|
|
54
|
+
rmSync(serverRootPath, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
mkdirSync(serverRootPath);
|
|
57
|
+
sandbox.restore();
|
|
36
58
|
});
|
|
37
59
|
|
|
38
|
-
it("should load
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await projectStore.finishedInitialization;
|
|
43
|
-
expect(await projectStore.listProjects()).toEqual([
|
|
44
|
-
{
|
|
45
|
-
name: "malloy-samples",
|
|
46
|
-
readme: expect.any(String),
|
|
47
|
-
resource: "/api/v0/projects/malloy-samples",
|
|
48
|
-
packages: expect.any(Array),
|
|
49
|
-
connections: expect.any(Array),
|
|
50
|
-
},
|
|
51
|
-
]);
|
|
60
|
+
it("should not load a package if the project does not exist", async () => {
|
|
61
|
+
await expect(
|
|
62
|
+
projectStore.getProject("non-existent-project"),
|
|
63
|
+
).rejects.toThrow();
|
|
52
64
|
});
|
|
53
65
|
|
|
54
|
-
it("should
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
it("should create and manage projects with connections", async () => {
|
|
67
|
+
// Create a project directory
|
|
68
|
+
const projectPath = path.join(serverRootPath, projectName);
|
|
69
|
+
mkdirSync(projectPath, { recursive: true });
|
|
70
|
+
|
|
71
|
+
// Create connections file
|
|
72
|
+
const connectionsPath = path.join(
|
|
73
|
+
projectPath,
|
|
74
|
+
"publisher.connections.json",
|
|
75
|
+
);
|
|
76
|
+
writeFileSync(connectionsPath, JSON.stringify(testConnections));
|
|
77
|
+
|
|
78
|
+
// Create publisher config
|
|
79
|
+
const publisherConfigPath = path.join(
|
|
80
|
+
serverRootPath,
|
|
81
|
+
"publisher.config.json",
|
|
82
|
+
);
|
|
83
|
+
writeFileSync(
|
|
84
|
+
publisherConfigPath,
|
|
85
|
+
JSON.stringify({
|
|
86
|
+
projects: [
|
|
87
|
+
{
|
|
88
|
+
name: projectName,
|
|
89
|
+
packages: [
|
|
90
|
+
{
|
|
91
|
+
name: projectName,
|
|
92
|
+
location: projectPath,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Test that the project can be retrieved
|
|
101
|
+
const project = await projectStore.getProject(projectName);
|
|
102
|
+
expect(project).toBeInstanceOf(Project);
|
|
103
|
+
expect(project.metadata.name).toBe(projectName);
|
|
67
104
|
});
|
|
68
105
|
|
|
69
|
-
it("should
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
106
|
+
it("should handle multiple projects", async () => {
|
|
107
|
+
const projectName1 = "project1";
|
|
108
|
+
const projectName2 = "project2";
|
|
109
|
+
const projectPath1 = path.join(serverRootPath, projectName1);
|
|
110
|
+
const projectPath2 = path.join(serverRootPath, projectName2);
|
|
111
|
+
|
|
112
|
+
// Create project directories
|
|
113
|
+
mkdirSync(projectPath1, { recursive: true });
|
|
114
|
+
mkdirSync(projectPath2, { recursive: true });
|
|
115
|
+
|
|
116
|
+
// Create connections files
|
|
117
|
+
writeFileSync(
|
|
118
|
+
path.join(projectPath1, "publisher.connections.json"),
|
|
119
|
+
JSON.stringify(testConnections),
|
|
120
|
+
);
|
|
121
|
+
writeFileSync(
|
|
122
|
+
path.join(projectPath2, "publisher.connections.json"),
|
|
123
|
+
JSON.stringify(testConnections),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Create publisher config
|
|
127
|
+
const publisherConfigPath = path.join(
|
|
128
|
+
serverRootPath,
|
|
129
|
+
"publisher.config.json",
|
|
130
|
+
);
|
|
131
|
+
writeFileSync(
|
|
132
|
+
publisherConfigPath,
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
projects: [
|
|
135
|
+
{
|
|
136
|
+
name: projectName1,
|
|
137
|
+
packages: [
|
|
138
|
+
{
|
|
139
|
+
name: projectName1,
|
|
140
|
+
location: projectPath1,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: projectName2,
|
|
146
|
+
packages: [
|
|
147
|
+
{
|
|
148
|
+
name: projectName2,
|
|
149
|
+
location: projectPath2,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
75
156
|
|
|
76
|
-
//
|
|
77
|
-
|
|
157
|
+
// Create a new project store that will read the configuration
|
|
158
|
+
const newProjectStore = new ProjectStore(serverRootPath);
|
|
159
|
+
await newProjectStore.finishedInitialization;
|
|
78
160
|
|
|
79
|
-
|
|
161
|
+
// Test that both projects can be listed
|
|
162
|
+
const projects = await newProjectStore.listProjects();
|
|
163
|
+
expect(projects).toBeInstanceOf(Array);
|
|
164
|
+
expect(projects.length).toBe(2);
|
|
165
|
+
expect(projects.map((p) => p.name)).toContain(projectName1);
|
|
166
|
+
expect(projects.map((p) => p.name)).toContain(projectName2);
|
|
80
167
|
});
|
|
81
168
|
|
|
82
|
-
it("should
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
169
|
+
it("should handle project updates", async () => {
|
|
170
|
+
// Create a project directory
|
|
171
|
+
const projectPath = path.join(serverRootPath, projectName);
|
|
172
|
+
mkdirSync(projectPath, { recursive: true });
|
|
173
|
+
|
|
174
|
+
// Create connections file
|
|
175
|
+
const connectionsPath = path.join(
|
|
176
|
+
projectPath,
|
|
177
|
+
"publisher.connections.json",
|
|
178
|
+
);
|
|
179
|
+
writeFileSync(connectionsPath, JSON.stringify(testConnections));
|
|
180
|
+
|
|
181
|
+
// Create publisher config
|
|
182
|
+
const publisherConfigPath = path.join(
|
|
183
|
+
serverRootPath,
|
|
184
|
+
"publisher.config.json",
|
|
185
|
+
);
|
|
186
|
+
writeFileSync(
|
|
187
|
+
publisherConfigPath,
|
|
188
|
+
JSON.stringify({
|
|
189
|
+
projects: [
|
|
190
|
+
{
|
|
191
|
+
name: projectName,
|
|
192
|
+
packages: [
|
|
193
|
+
{
|
|
194
|
+
name: projectName,
|
|
195
|
+
location: projectPath,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
104
200
|
}),
|
|
105
201
|
);
|
|
106
|
-
await projectStore.deleteProject("malloy-samples");
|
|
107
|
-
expect(await projectStore.listProjects()).toEqual([]);
|
|
108
|
-
await projectStore.addProject({
|
|
109
|
-
name: "malloy-samples",
|
|
110
|
-
});
|
|
111
202
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
).toHaveProperty("metadata", {
|
|
115
|
-
name: "malloy-samples",
|
|
116
|
-
resource: "/api/v0/projects/malloy-samples",
|
|
117
|
-
location: expect.any(String),
|
|
118
|
-
});
|
|
203
|
+
// Get the project
|
|
204
|
+
const project = await projectStore.getProject(projectName);
|
|
119
205
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
name: "malloy-samples",
|
|
125
|
-
resource: "/api/v0/projects/malloy-samples",
|
|
206
|
+
// Update the project
|
|
207
|
+
const updatedProject = await project.update({
|
|
208
|
+
name: projectName,
|
|
209
|
+
readme: "Updated README content",
|
|
126
210
|
});
|
|
211
|
+
|
|
212
|
+
expect(updatedProject.metadata.readme).toBe("Updated README content");
|
|
127
213
|
});
|
|
128
214
|
|
|
129
|
-
it("should
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
215
|
+
it("should handle project reload", async () => {
|
|
216
|
+
// Create a project directory
|
|
217
|
+
const projectPath = path.join(serverRootPath, projectName);
|
|
218
|
+
mkdirSync(projectPath, { recursive: true });
|
|
219
|
+
|
|
220
|
+
// Create connections file
|
|
221
|
+
const connectionsPath = path.join(
|
|
222
|
+
projectPath,
|
|
223
|
+
"publisher.connections.json",
|
|
224
|
+
);
|
|
225
|
+
writeFileSync(connectionsPath, JSON.stringify(testConnections));
|
|
226
|
+
|
|
227
|
+
// Create publisher config
|
|
228
|
+
const publisherConfigPath = path.join(
|
|
229
|
+
serverRootPath,
|
|
230
|
+
"publisher.config.json",
|
|
231
|
+
);
|
|
232
|
+
writeFileSync(
|
|
233
|
+
publisherConfigPath,
|
|
234
|
+
JSON.stringify({
|
|
235
|
+
projects: [
|
|
236
|
+
{
|
|
237
|
+
name: projectName,
|
|
238
|
+
packages: [
|
|
239
|
+
{
|
|
240
|
+
name: projectName,
|
|
241
|
+
location: projectPath,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
149
246
|
}),
|
|
150
|
-
)
|
|
247
|
+
);
|
|
151
248
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
249
|
+
// Get the project
|
|
250
|
+
const project1 = await projectStore.getProject(projectName);
|
|
251
|
+
|
|
252
|
+
// Get the project again with reload=true
|
|
253
|
+
const project2 = await projectStore.getProject(projectName, true);
|
|
254
|
+
|
|
255
|
+
expect(project1).toBeInstanceOf(Project);
|
|
256
|
+
expect(project2).toBeInstanceOf(Project);
|
|
257
|
+
expect(project1.metadata.name).toBe(project2.metadata.name as string);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("should handle missing project paths", async () => {
|
|
261
|
+
// Create publisher config with non-existent project path
|
|
262
|
+
const publisherConfigPath = path.join(
|
|
263
|
+
serverRootPath,
|
|
264
|
+
"publisher.config.json",
|
|
265
|
+
);
|
|
266
|
+
writeFileSync(
|
|
267
|
+
publisherConfigPath,
|
|
268
|
+
JSON.stringify({
|
|
269
|
+
projects: [
|
|
270
|
+
{
|
|
271
|
+
name: projectName,
|
|
272
|
+
packages: [
|
|
273
|
+
{
|
|
274
|
+
name: projectName,
|
|
275
|
+
location: "/non/existent/path",
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
],
|
|
157
280
|
}),
|
|
158
|
-
)
|
|
281
|
+
);
|
|
159
282
|
|
|
160
|
-
//
|
|
161
|
-
expect(projectStore.
|
|
162
|
-
|
|
283
|
+
// Test that getting the project throws an error
|
|
284
|
+
await expect(projectStore.getProject(projectName)).rejects.toThrow();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should handle invalid publisher config", async () => {
|
|
288
|
+
// Create invalid publisher config
|
|
289
|
+
const publisherConfigPath = path.join(
|
|
290
|
+
serverRootPath,
|
|
291
|
+
"publisher.config.json",
|
|
163
292
|
);
|
|
293
|
+
writeFileSync(publisherConfigPath, "invalid json");
|
|
164
294
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
connections: expect.any(Array),
|
|
173
|
-
},
|
|
174
|
-
]);
|
|
295
|
+
// Create a new project store that will read the invalid config
|
|
296
|
+
const newProjectStore = new ProjectStore(serverRootPath);
|
|
297
|
+
|
|
298
|
+
// Test that the project store handles invalid JSON gracefully by falling back to empty config
|
|
299
|
+
await newProjectStore.finishedInitialization;
|
|
300
|
+
const projects = await newProjectStore.listProjects();
|
|
301
|
+
expect(projects).toEqual([]);
|
|
175
302
|
});
|
|
176
303
|
|
|
177
|
-
it("should
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
304
|
+
it("should handle concurrent project access", async () => {
|
|
305
|
+
// Create a project directory
|
|
306
|
+
const projectPath = path.join(serverRootPath, projectName);
|
|
307
|
+
mkdirSync(projectPath, { recursive: true });
|
|
308
|
+
|
|
309
|
+
// Create connections file
|
|
310
|
+
const connectionsPath = path.join(
|
|
311
|
+
projectPath,
|
|
312
|
+
"publisher.connections.json",
|
|
313
|
+
);
|
|
314
|
+
writeFileSync(connectionsPath, JSON.stringify(testConnections));
|
|
315
|
+
|
|
316
|
+
// Create publisher config
|
|
317
|
+
const publisherConfigPath = path.join(
|
|
318
|
+
serverRootPath,
|
|
319
|
+
"publisher.config.json",
|
|
320
|
+
);
|
|
321
|
+
writeFileSync(
|
|
322
|
+
publisherConfigPath,
|
|
323
|
+
JSON.stringify({
|
|
324
|
+
projects: [
|
|
325
|
+
{
|
|
326
|
+
name: projectName,
|
|
327
|
+
packages: [
|
|
328
|
+
{
|
|
329
|
+
name: projectName,
|
|
330
|
+
location: projectPath,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
}),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Test concurrent access to the same project
|
|
339
|
+
const promises = Array.from({ length: 5 }, () =>
|
|
340
|
+
projectStore.getProject(projectName),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const projects = await Promise.all(promises);
|
|
344
|
+
|
|
345
|
+
expect(projects).toHaveLength(5);
|
|
346
|
+
projects.forEach((project) => {
|
|
347
|
+
expect(project).toBeInstanceOf(Project);
|
|
348
|
+
expect(project.metadata.name).toBe(projectName);
|
|
349
|
+
});
|
|
188
350
|
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe("Project Service Error Recovery", () => {
|
|
354
|
+
let sandbox: sinon.SinonSandbox;
|
|
355
|
+
let projectStore: ProjectStore;
|
|
356
|
+
const serverRootPath = "/tmp/pathways-worker-publisher-error-recovery-test";
|
|
357
|
+
const projectName = "organizationName-projectName-error-recovery";
|
|
358
|
+
const testConnections: Connection[] = [
|
|
359
|
+
{
|
|
360
|
+
name: "testConnection",
|
|
361
|
+
type: "postgres",
|
|
362
|
+
postgresConnection: {
|
|
363
|
+
host: "host",
|
|
364
|
+
port: 1234,
|
|
365
|
+
databaseName: "databaseName",
|
|
366
|
+
userName: "userName",
|
|
367
|
+
password: "password",
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
];
|
|
189
371
|
|
|
190
|
-
|
|
372
|
+
beforeEach(async () => {
|
|
373
|
+
sandbox = sinon.createSandbox();
|
|
374
|
+
mkdirSync(serverRootPath, { recursive: true });
|
|
375
|
+
|
|
376
|
+
// Mock the configuration to prevent initialization errors
|
|
377
|
+
mock(isPublisherConfigFrozen).mockReturnValue(false);
|
|
191
378
|
mock.module("../config", () => ({
|
|
192
379
|
isPublisherConfigFrozen: () => false,
|
|
193
380
|
}));
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
381
|
+
|
|
382
|
+
// Create project store after mocking
|
|
383
|
+
projectStore = new ProjectStore(serverRootPath);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
afterEach(async () => {
|
|
387
|
+
sandbox.restore();
|
|
388
|
+
if (existsSync(serverRootPath)) {
|
|
389
|
+
rmSync(serverRootPath, { recursive: true, force: true });
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe("Project Loading Error Recovery", () => {
|
|
394
|
+
it("should handle missing project directories gracefully", async () => {
|
|
395
|
+
// Create publisher config with missing project directory
|
|
396
|
+
const publisherConfigPath = path.join(
|
|
397
|
+
serverRootPath,
|
|
398
|
+
"publisher.config.json",
|
|
399
|
+
);
|
|
400
|
+
writeFileSync(
|
|
401
|
+
publisherConfigPath,
|
|
402
|
+
JSON.stringify({
|
|
403
|
+
projects: [
|
|
404
|
+
{
|
|
405
|
+
name: projectName,
|
|
406
|
+
packages: [
|
|
407
|
+
{
|
|
408
|
+
name: projectName,
|
|
409
|
+
location: path.join(
|
|
410
|
+
serverRootPath,
|
|
411
|
+
"missing-project",
|
|
412
|
+
),
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
}),
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// Test that the project store handles the missing directory
|
|
421
|
+
await expect(projectStore.getProject(projectName)).rejects.toThrow();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("should handle corrupted connection files", async () => {
|
|
425
|
+
// Create a project directory
|
|
426
|
+
const projectPath = path.join(serverRootPath, projectName);
|
|
427
|
+
mkdirSync(projectPath, { recursive: true });
|
|
428
|
+
|
|
429
|
+
// Create corrupted connections file
|
|
430
|
+
const connectionsPath = path.join(
|
|
431
|
+
projectPath,
|
|
432
|
+
"publisher.connections.json",
|
|
433
|
+
);
|
|
434
|
+
writeFileSync(connectionsPath, "invalid json");
|
|
435
|
+
|
|
436
|
+
// Create publisher config
|
|
437
|
+
const publisherConfigPath = path.join(
|
|
438
|
+
serverRootPath,
|
|
439
|
+
"publisher.config.json",
|
|
440
|
+
);
|
|
441
|
+
writeFileSync(
|
|
442
|
+
publisherConfigPath,
|
|
443
|
+
JSON.stringify({
|
|
444
|
+
projects: [
|
|
445
|
+
{
|
|
446
|
+
name: projectName,
|
|
447
|
+
packages: [
|
|
448
|
+
{
|
|
449
|
+
name: projectName,
|
|
450
|
+
location: projectPath,
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
}),
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
// Test that the project store handles corrupted connection files gracefully
|
|
459
|
+
// (The current implementation loads the project even with corrupted connection files)
|
|
460
|
+
const project = await projectStore.getProject(projectName);
|
|
461
|
+
expect(project).toBeInstanceOf(Project);
|
|
462
|
+
expect(project.metadata.name).toBe(projectName);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
describe("Project Store State Management", () => {
|
|
467
|
+
it("should maintain consistent state after errors", async () => {
|
|
468
|
+
// Create a valid project first
|
|
469
|
+
const projectPath = path.join(serverRootPath, projectName);
|
|
470
|
+
mkdirSync(projectPath, { recursive: true });
|
|
471
|
+
writeFileSync(
|
|
472
|
+
path.join(projectPath, "publisher.connections.json"),
|
|
473
|
+
JSON.stringify(testConnections),
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const publisherConfigPath = path.join(
|
|
477
|
+
serverRootPath,
|
|
478
|
+
"publisher.config.json",
|
|
479
|
+
);
|
|
480
|
+
writeFileSync(
|
|
481
|
+
publisherConfigPath,
|
|
482
|
+
JSON.stringify({
|
|
483
|
+
projects: [
|
|
484
|
+
{
|
|
485
|
+
name: projectName,
|
|
486
|
+
packages: [
|
|
487
|
+
{
|
|
488
|
+
name: projectName,
|
|
489
|
+
location: projectPath,
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
}),
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// Get the project successfully
|
|
498
|
+
const project = await projectStore.getProject(projectName);
|
|
499
|
+
expect(project).toBeInstanceOf(Project);
|
|
500
|
+
|
|
501
|
+
// Try to get a non-existent project
|
|
502
|
+
await expect(
|
|
503
|
+
projectStore.getProject("non-existent"),
|
|
504
|
+
).rejects.toThrow();
|
|
505
|
+
|
|
506
|
+
// Verify the original project is still accessible
|
|
507
|
+
const projectAgain = await projectStore.getProject(projectName);
|
|
508
|
+
expect(projectAgain).toBeInstanceOf(Project);
|
|
509
|
+
expect(projectAgain.metadata.name).toBe(projectName);
|
|
510
|
+
});
|
|
199
511
|
});
|
|
200
512
|
});
|