@malloy-publisher/server 0.0.382-dev → 0.2.1
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.docker.md +145 -20
- package/README.md +65 -12
- package/dist/app/api-doc.html +5 -0
- package/dist/app/api-doc.yaml +3929 -534
- package/dist/app/assets/EnvironmentPage-CHq5BF4l.js +1 -0
- package/dist/app/assets/HomePage-CIp-pJCa.js +1 -0
- package/dist/app/assets/LightMode-mHHPjEeA.js +1 -0
- package/dist/app/assets/MainPage-CylxO5Yu.js +2 -0
- package/dist/app/assets/MaterializationsPage-a0vjqxib.js +1 -0
- package/dist/app/assets/ModelPage-Cz-MrSZQ.js +1 -0
- package/dist/app/assets/PackagePage-Cdg5D7AN.js +1 -0
- package/dist/app/assets/RouteError-B5SIyn6G.js +1 -0
- package/dist/app/assets/ThemeEditorPage-B62rRc-y.js +1 -0
- package/dist/app/assets/WorkbookPage-45cG1Gx6.js +1 -0
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-DyU47reh.es-DY9b3WjS.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-5eLCcNmP.css +1 -0
- package/dist/app/assets/index-C5ClsnS7.js +1774 -0
- package/dist/app/assets/index-DF9tVqwN.js +1 -0
- package/dist/app/assets/index-DSoCUF-f.js +2725 -0
- package/dist/app/assets/index-DgVRz2wm.js +2 -0
- package/dist/app/assets/index-P-pF8nU5.js +632 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +8 -4
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
- package/dist/default-publisher.config.json +23 -0
- package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
- package/dist/package_load_worker.mjs +15219 -0
- package/dist/runtime/publisher.js +331 -0
- package/dist/{server.js → server.mjs} +141276 -74705
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
- package/package.json +41 -28
- package/publisher.config.example.bigquery.json +33 -0
- package/publisher.config.example.duckdb.json +23 -0
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -38
- package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
- package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
- package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
- package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
- package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
- package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
- package/dist/app/assets/index-15BOvhp0.js +0 -456
- package/dist/app/assets/index-Bb2jqquW.js +0 -676
- package/dist/app/assets/index-CMlGQMcl.css +0 -1
- package/dist/app/assets/index-D68X76-7.js +0 -1276
- package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.json +0 -33
- package/src/config.spec.ts +0 -850
- package/src/config.ts +0 -305
- package/src/constants.ts +0 -10
- package/src/controller/compile.controller.ts +0 -35
- package/src/controller/connection.controller.ts +0 -515
- package/src/controller/database.controller.ts +0 -21
- package/src/controller/manifest.controller.ts +0 -29
- package/src/controller/materialization.controller.ts +0 -125
- package/src/controller/model.controller.ts +0 -107
- package/src/controller/package.controller.ts +0 -182
- package/src/controller/query.controller.ts +0 -60
- package/src/controller/watch-mode.controller.ts +0 -98
- package/src/data_styles.ts +0 -71
- package/src/dto/connection.dto.spec.ts +0 -131
- package/src/dto/connection.dto.ts +0 -174
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -12
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/errors.ts +0 -116
- package/src/health.ts +0 -189
- package/src/instrumentation.ts +0 -148
- package/src/logger.ts +0 -154
- package/src/mcp/error_messages.ts +0 -199
- package/src/mcp/handler_utils.ts +0 -239
- package/src/mcp/mcp_constants.ts +0 -22
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/model_resource.ts +0 -153
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -370
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/mcp/resources/query_resource.ts +0 -117
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/server.ts +0 -62
- package/src/mcp/tools/discovery_tools.ts +0 -258
- package/src/mcp/tools/execute_query_tool.ts +0 -265
- package/src/server.ts +0 -1195
- package/src/service/connection.spec.ts +0 -1331
- package/src/service/connection.ts +0 -1511
- package/src/service/connection_service.spec.ts +0 -641
- package/src/service/connection_service.ts +0 -181
- package/src/service/db_utils.spec.ts +0 -714
- package/src/service/db_utils.ts +0 -1227
- package/src/service/filter.spec.ts +0 -447
- package/src/service/filter.ts +0 -337
- package/src/service/filter_integration.spec.ts +0 -825
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/manifest_service.spec.ts +0 -201
- package/src/service/manifest_service.ts +0 -106
- package/src/service/materialization_service.spec.ts +0 -648
- package/src/service/materialization_service.ts +0 -929
- package/src/service/materialized_table_gc.spec.ts +0 -383
- package/src/service/materialized_table_gc.ts +0 -279
- package/src/service/model.spec.ts +0 -257
- package/src/service/model.ts +0 -1069
- package/src/service/package.spec.ts +0 -236
- package/src/service/package.ts +0 -463
- package/src/service/project.ts +0 -681
- package/src/service/project_compile.spec.ts +0 -197
- package/src/service/project_store.spec.ts +0 -859
- package/src/service/project_store.ts +0 -1531
- package/src/service/quoting.ts +0 -41
- package/src/service/resolve_project.ts +0 -13
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/DatabaseInterface.ts +0 -180
- package/src/storage/StorageManager.mock.ts +0 -59
- package/src/storage/StorageManager.ts +0 -210
- package/src/storage/duckdb/ConnectionRepository.ts +0 -141
- package/src/storage/duckdb/DuckDBConnection.ts +0 -177
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/DuckDBRepository.ts +0 -224
- package/src/storage/duckdb/ManifestRepository.ts +0 -119
- package/src/storage/duckdb/MaterializationRepository.ts +0 -249
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/ProjectRepository.ts +0 -148
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/duckdb/schema.ts +0 -147
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
- package/src/utils.ts +0 -13
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mcp_test_setup.ts +0 -344
- package/tests/harness/mocks.ts +0 -37
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
- package/tests/unit/ducklake/ducklake.test.ts +0 -950
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
- package/tsconfig.json +0 -32
|
@@ -1,859 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import * as sinon from "sinon";
|
|
5
|
-
import { components } from "../api";
|
|
6
|
-
import { isPublisherConfigFrozen } from "../config";
|
|
7
|
-
import { TEMP_DIR_PATH } from "../constants";
|
|
8
|
-
import { Project } from "./project";
|
|
9
|
-
import { ProjectStore } from "./project_store";
|
|
10
|
-
|
|
11
|
-
type MockData = Record<string, unknown>;
|
|
12
|
-
|
|
13
|
-
mock.module("../storage/StorageManager", () => {
|
|
14
|
-
return {
|
|
15
|
-
StorageManager: class MockStorageManager {
|
|
16
|
-
async initialize(_reInit?: boolean): Promise<void> {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
getRepository() {
|
|
21
|
-
return {
|
|
22
|
-
// ===== PROJECT METHODS =====
|
|
23
|
-
listProjects: async (): Promise<unknown[]> => [],
|
|
24
|
-
|
|
25
|
-
getProjectById: async (
|
|
26
|
-
id: string,
|
|
27
|
-
): Promise<MockData | null> => ({
|
|
28
|
-
id,
|
|
29
|
-
name: "test-project",
|
|
30
|
-
path: "/test/path",
|
|
31
|
-
description: "Test description",
|
|
32
|
-
metadata: {},
|
|
33
|
-
createdAt: new Date(),
|
|
34
|
-
updatedAt: new Date(),
|
|
35
|
-
}),
|
|
36
|
-
|
|
37
|
-
getProjectByName: async (
|
|
38
|
-
_name: string,
|
|
39
|
-
): Promise<MockData | null> => {
|
|
40
|
-
// Return null to simulate "project doesn't exist yet"
|
|
41
|
-
return null;
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
createProject: async (data: MockData): Promise<MockData> => ({
|
|
45
|
-
id: "test-project-id",
|
|
46
|
-
name: data.name,
|
|
47
|
-
path: data.path,
|
|
48
|
-
description: data.description,
|
|
49
|
-
metadata: data.metadata,
|
|
50
|
-
createdAt: new Date(),
|
|
51
|
-
updatedAt: new Date(),
|
|
52
|
-
}),
|
|
53
|
-
|
|
54
|
-
updateProject: async (
|
|
55
|
-
id: string,
|
|
56
|
-
data: MockData,
|
|
57
|
-
): Promise<MockData> => ({
|
|
58
|
-
id,
|
|
59
|
-
name: "test-project",
|
|
60
|
-
path: "/test/path",
|
|
61
|
-
description: data.description,
|
|
62
|
-
metadata: {
|
|
63
|
-
...(data.metadata || {}),
|
|
64
|
-
readme: data.readme,
|
|
65
|
-
},
|
|
66
|
-
createdAt: new Date(),
|
|
67
|
-
updatedAt: new Date(),
|
|
68
|
-
}),
|
|
69
|
-
|
|
70
|
-
deleteProject: async (_id: string): Promise<void> => {},
|
|
71
|
-
|
|
72
|
-
// ===== PACKAGE METHODS =====
|
|
73
|
-
listPackages: async (
|
|
74
|
-
_projectId: string,
|
|
75
|
-
): Promise<unknown[]> => [],
|
|
76
|
-
|
|
77
|
-
getPackageById: async (
|
|
78
|
-
id: string,
|
|
79
|
-
): Promise<MockData | null> => ({
|
|
80
|
-
id,
|
|
81
|
-
projectId: "test-project-id",
|
|
82
|
-
name: "test-package",
|
|
83
|
-
description: "Test package",
|
|
84
|
-
manifestPath: "/test/manifest.json",
|
|
85
|
-
metadata: {},
|
|
86
|
-
createdAt: new Date(),
|
|
87
|
-
updatedAt: new Date(),
|
|
88
|
-
}),
|
|
89
|
-
|
|
90
|
-
getPackageByName: async (
|
|
91
|
-
_projectId: string,
|
|
92
|
-
_name: string,
|
|
93
|
-
): Promise<MockData | null> => null,
|
|
94
|
-
|
|
95
|
-
createPackage: async (data: MockData): Promise<MockData> => ({
|
|
96
|
-
id: "test-package-id",
|
|
97
|
-
projectId: data.projectId,
|
|
98
|
-
name: data.name,
|
|
99
|
-
description: data.description,
|
|
100
|
-
manifestPath: data.manifestPath,
|
|
101
|
-
metadata: data.metadata,
|
|
102
|
-
createdAt: new Date(),
|
|
103
|
-
updatedAt: new Date(),
|
|
104
|
-
}),
|
|
105
|
-
|
|
106
|
-
updatePackage: async (
|
|
107
|
-
id: string,
|
|
108
|
-
data: MockData,
|
|
109
|
-
): Promise<MockData> => ({
|
|
110
|
-
id,
|
|
111
|
-
projectId: "test-project-id",
|
|
112
|
-
name: "test-package",
|
|
113
|
-
description: data.description,
|
|
114
|
-
manifestPath: "/test/manifest.json",
|
|
115
|
-
metadata: data.metadata || {},
|
|
116
|
-
createdAt: new Date(),
|
|
117
|
-
updatedAt: new Date(),
|
|
118
|
-
}),
|
|
119
|
-
|
|
120
|
-
deletePackage: async (_id: string): Promise<void> => {},
|
|
121
|
-
|
|
122
|
-
// ===== CONNECTION METHODS =====
|
|
123
|
-
listConnections: async (
|
|
124
|
-
_projectId: string,
|
|
125
|
-
): Promise<unknown[]> => [],
|
|
126
|
-
|
|
127
|
-
getConnectionById: async (
|
|
128
|
-
id: string,
|
|
129
|
-
): Promise<MockData | null> => ({
|
|
130
|
-
id,
|
|
131
|
-
projectId: "test-project-id",
|
|
132
|
-
name: "test-connection",
|
|
133
|
-
type: "postgres",
|
|
134
|
-
config: {},
|
|
135
|
-
createdAt: new Date(),
|
|
136
|
-
updatedAt: new Date(),
|
|
137
|
-
}),
|
|
138
|
-
|
|
139
|
-
getConnectionByName: async (
|
|
140
|
-
_projectId: string,
|
|
141
|
-
_name: string,
|
|
142
|
-
): Promise<MockData | null> => null,
|
|
143
|
-
|
|
144
|
-
createConnection: async (data: MockData): Promise<MockData> => ({
|
|
145
|
-
id: "test-connection-id",
|
|
146
|
-
projectId: data.projectId,
|
|
147
|
-
name: data.name,
|
|
148
|
-
type: data.type,
|
|
149
|
-
config: data.config,
|
|
150
|
-
createdAt: new Date(),
|
|
151
|
-
updatedAt: new Date(),
|
|
152
|
-
}),
|
|
153
|
-
|
|
154
|
-
updateConnection: async (
|
|
155
|
-
id: string,
|
|
156
|
-
data: MockData,
|
|
157
|
-
): Promise<MockData> => ({
|
|
158
|
-
id,
|
|
159
|
-
projectId: "test-project-id",
|
|
160
|
-
name: "test-connection",
|
|
161
|
-
type: "postgres",
|
|
162
|
-
config: data.config || {},
|
|
163
|
-
createdAt: new Date(),
|
|
164
|
-
updatedAt: new Date(),
|
|
165
|
-
}),
|
|
166
|
-
|
|
167
|
-
deleteConnection: async (_id: string): Promise<void> => {},
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
StorageConfig: {} as Record<string, unknown>,
|
|
172
|
-
};
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
type Connection = components["schemas"]["Connection"];
|
|
176
|
-
|
|
177
|
-
const serverRootPath = path.join(
|
|
178
|
-
TEMP_DIR_PATH,
|
|
179
|
-
"pathways-worker-publisher-project-store-test",
|
|
180
|
-
);
|
|
181
|
-
const projectName = "organizationName-projectName";
|
|
182
|
-
|
|
183
|
-
let sandbox: sinon.SinonSandbox;
|
|
184
|
-
|
|
185
|
-
describe("ProjectStore Service", () => {
|
|
186
|
-
let projectStore: ProjectStore;
|
|
187
|
-
|
|
188
|
-
beforeEach(async () => {
|
|
189
|
-
// Clean up any existing test directory
|
|
190
|
-
if (existsSync(serverRootPath)) {
|
|
191
|
-
rmSync(serverRootPath, { recursive: true, force: true });
|
|
192
|
-
}
|
|
193
|
-
mkdirSync(serverRootPath);
|
|
194
|
-
sandbox = sinon.createSandbox();
|
|
195
|
-
|
|
196
|
-
// Mock the configuration to prevent initialization errors
|
|
197
|
-
mock(isPublisherConfigFrozen).mockReturnValue(false);
|
|
198
|
-
mock.module("../config", () => ({
|
|
199
|
-
isPublisherConfigFrozen: () => false,
|
|
200
|
-
}));
|
|
201
|
-
|
|
202
|
-
// Create project store after mocking
|
|
203
|
-
projectStore = new ProjectStore(serverRootPath);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
afterEach(async () => {
|
|
207
|
-
// Clean up the test directory after each test
|
|
208
|
-
if (existsSync(serverRootPath)) {
|
|
209
|
-
rmSync(serverRootPath, { recursive: true, force: true });
|
|
210
|
-
}
|
|
211
|
-
mkdirSync(serverRootPath);
|
|
212
|
-
sandbox.restore();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("should not load a package if the project does not exist", async () => {
|
|
216
|
-
await expect(
|
|
217
|
-
projectStore.getProject("non-existent-project"),
|
|
218
|
-
).rejects.toThrow();
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it(
|
|
222
|
-
"should create and manage projects with connections",
|
|
223
|
-
async () => {
|
|
224
|
-
// Create a project directory
|
|
225
|
-
const projectPath = path.join(serverRootPath, projectName);
|
|
226
|
-
mkdirSync(projectPath, { recursive: true });
|
|
227
|
-
// Create publisher.json manifest file
|
|
228
|
-
writeFileSync(
|
|
229
|
-
path.join(projectPath, "publisher.json"),
|
|
230
|
-
JSON.stringify({
|
|
231
|
-
name: projectName,
|
|
232
|
-
description: "Test package",
|
|
233
|
-
}),
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
// Create publisher config
|
|
237
|
-
const publisherConfigPath = path.join(
|
|
238
|
-
serverRootPath,
|
|
239
|
-
"publisher.config.json",
|
|
240
|
-
);
|
|
241
|
-
writeFileSync(
|
|
242
|
-
publisherConfigPath,
|
|
243
|
-
JSON.stringify({
|
|
244
|
-
frozenConfig: false,
|
|
245
|
-
projects: [
|
|
246
|
-
{
|
|
247
|
-
name: projectName,
|
|
248
|
-
packages: [
|
|
249
|
-
{
|
|
250
|
-
name: projectName,
|
|
251
|
-
location: projectPath,
|
|
252
|
-
},
|
|
253
|
-
],
|
|
254
|
-
connections: [
|
|
255
|
-
{
|
|
256
|
-
name: "testConnection",
|
|
257
|
-
type: "postgres",
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
}),
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
// Test that the project can be retrieved
|
|
266
|
-
const project = await projectStore.getProject(projectName);
|
|
267
|
-
expect(project).toBeInstanceOf(Project);
|
|
268
|
-
expect(project.metadata.name).toBe(projectName);
|
|
269
|
-
},
|
|
270
|
-
{ timeout: 30000 },
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
it("should handle multiple projects", async () => {
|
|
274
|
-
const projectName1 = "project1";
|
|
275
|
-
const projectName2 = "project2";
|
|
276
|
-
const projectPath1 = path.join(serverRootPath, projectName1);
|
|
277
|
-
const projectPath2 = path.join(serverRootPath, projectName2);
|
|
278
|
-
|
|
279
|
-
// Create project directories
|
|
280
|
-
mkdirSync(projectPath1, { recursive: true });
|
|
281
|
-
mkdirSync(projectPath2, { recursive: true });
|
|
282
|
-
|
|
283
|
-
// Create publisher config
|
|
284
|
-
const publisherConfigPath = path.join(
|
|
285
|
-
serverRootPath,
|
|
286
|
-
"publisher.config.json",
|
|
287
|
-
);
|
|
288
|
-
writeFileSync(
|
|
289
|
-
publisherConfigPath,
|
|
290
|
-
JSON.stringify({
|
|
291
|
-
frozenConfig: false,
|
|
292
|
-
projects: [
|
|
293
|
-
{
|
|
294
|
-
name: projectName1,
|
|
295
|
-
packages: [
|
|
296
|
-
{
|
|
297
|
-
name: projectName1,
|
|
298
|
-
location: projectPath1,
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
connections: [
|
|
302
|
-
{
|
|
303
|
-
name: "testConnection",
|
|
304
|
-
type: "postgres",
|
|
305
|
-
},
|
|
306
|
-
],
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
name: projectName2,
|
|
310
|
-
packages: [
|
|
311
|
-
{
|
|
312
|
-
name: projectName2,
|
|
313
|
-
location: projectPath2,
|
|
314
|
-
},
|
|
315
|
-
],
|
|
316
|
-
connections: [
|
|
317
|
-
{
|
|
318
|
-
name: "testConnection2",
|
|
319
|
-
type: "bigquery",
|
|
320
|
-
bigqueryConnection: {},
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
}),
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
// Create a new project store that will read the configuration
|
|
329
|
-
const newProjectStore = new ProjectStore(serverRootPath);
|
|
330
|
-
await newProjectStore.finishedInitialization;
|
|
331
|
-
|
|
332
|
-
// Test that both projects can be listed
|
|
333
|
-
const projects = await newProjectStore.listProjects();
|
|
334
|
-
expect(projects).toBeInstanceOf(Array);
|
|
335
|
-
expect(projects.length).toBe(2);
|
|
336
|
-
expect(projects.map((p) => p.name)).toContain(projectName1);
|
|
337
|
-
expect(projects.map((p) => p.name)).toContain(projectName2);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it("should handle project updates", async () => {
|
|
341
|
-
// Create a project directory
|
|
342
|
-
const projectPath = path.join(serverRootPath, projectName);
|
|
343
|
-
mkdirSync(projectPath, { recursive: true });
|
|
344
|
-
// Create publisher.json manifest file
|
|
345
|
-
writeFileSync(
|
|
346
|
-
path.join(projectPath, "publisher.json"),
|
|
347
|
-
JSON.stringify({
|
|
348
|
-
name: projectName,
|
|
349
|
-
description: "Test package",
|
|
350
|
-
}),
|
|
351
|
-
);
|
|
352
|
-
// Create publisher config
|
|
353
|
-
const publisherConfigPath = path.join(
|
|
354
|
-
serverRootPath,
|
|
355
|
-
"publisher.config.json",
|
|
356
|
-
);
|
|
357
|
-
writeFileSync(
|
|
358
|
-
publisherConfigPath,
|
|
359
|
-
JSON.stringify({
|
|
360
|
-
frozenConfig: false,
|
|
361
|
-
projects: [
|
|
362
|
-
{
|
|
363
|
-
name: projectName,
|
|
364
|
-
packages: [
|
|
365
|
-
{
|
|
366
|
-
name: projectName,
|
|
367
|
-
location: projectPath,
|
|
368
|
-
},
|
|
369
|
-
],
|
|
370
|
-
},
|
|
371
|
-
],
|
|
372
|
-
}),
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
await projectStore.finishedInitialization;
|
|
376
|
-
|
|
377
|
-
// Get the project
|
|
378
|
-
const project = await projectStore.getProject(projectName);
|
|
379
|
-
|
|
380
|
-
// Update the project
|
|
381
|
-
await project.update({
|
|
382
|
-
name: projectName,
|
|
383
|
-
readme: "Updated README content",
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
const readmePath = path.join(
|
|
387
|
-
serverRootPath,
|
|
388
|
-
"publisher_data",
|
|
389
|
-
projectName,
|
|
390
|
-
"README.md",
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
expect(existsSync(readmePath)).toBe(true);
|
|
394
|
-
const readmeContent = readFileSync(readmePath, "utf-8");
|
|
395
|
-
expect(readmeContent).toBe("Updated README content");
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it(
|
|
399
|
-
"should handle project reload",
|
|
400
|
-
async () => {
|
|
401
|
-
// Create a project directory
|
|
402
|
-
const projectPath = path.join(serverRootPath, projectName);
|
|
403
|
-
mkdirSync(projectPath, { recursive: true });
|
|
404
|
-
// Create publisher.json manifest file
|
|
405
|
-
writeFileSync(
|
|
406
|
-
path.join(projectPath, "publisher.json"),
|
|
407
|
-
JSON.stringify({
|
|
408
|
-
name: projectName,
|
|
409
|
-
description: "Test package",
|
|
410
|
-
}),
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
// Create publisher config
|
|
414
|
-
const publisherConfigPath = path.join(
|
|
415
|
-
serverRootPath,
|
|
416
|
-
"publisher.config.json",
|
|
417
|
-
);
|
|
418
|
-
writeFileSync(
|
|
419
|
-
publisherConfigPath,
|
|
420
|
-
JSON.stringify({
|
|
421
|
-
projects: [
|
|
422
|
-
{
|
|
423
|
-
name: projectName,
|
|
424
|
-
packages: [
|
|
425
|
-
{
|
|
426
|
-
name: projectName,
|
|
427
|
-
location: projectPath,
|
|
428
|
-
},
|
|
429
|
-
],
|
|
430
|
-
},
|
|
431
|
-
],
|
|
432
|
-
}),
|
|
433
|
-
);
|
|
434
|
-
|
|
435
|
-
// Get the project
|
|
436
|
-
const project1 = await projectStore.getProject(projectName);
|
|
437
|
-
|
|
438
|
-
// Get the project again with reload=true
|
|
439
|
-
const project2 = await projectStore.getProject(projectName, true);
|
|
440
|
-
|
|
441
|
-
expect(project1).toBeInstanceOf(Project);
|
|
442
|
-
expect(project2).toBeInstanceOf(Project);
|
|
443
|
-
expect(project1.metadata.name).toBe(project2.metadata.name as string);
|
|
444
|
-
},
|
|
445
|
-
{ timeout: 30000 },
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
it("should handle missing project paths", async () => {
|
|
449
|
-
// Create publisher config with non-existent project path
|
|
450
|
-
const publisherConfigPath = path.join(
|
|
451
|
-
serverRootPath,
|
|
452
|
-
"publisher.config.json",
|
|
453
|
-
);
|
|
454
|
-
writeFileSync(
|
|
455
|
-
publisherConfigPath,
|
|
456
|
-
JSON.stringify({
|
|
457
|
-
projects: [
|
|
458
|
-
{
|
|
459
|
-
name: projectName,
|
|
460
|
-
packages: [
|
|
461
|
-
{
|
|
462
|
-
name: projectName,
|
|
463
|
-
location: "/non/existent/path",
|
|
464
|
-
},
|
|
465
|
-
],
|
|
466
|
-
},
|
|
467
|
-
],
|
|
468
|
-
}),
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
// Test that getting the project throws an error
|
|
472
|
-
await expect(projectStore.getProject(projectName)).rejects.toThrow();
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
it("should handle invalid publisher config", async () => {
|
|
476
|
-
// Create invalid publisher config
|
|
477
|
-
const publisherConfigPath = path.join(
|
|
478
|
-
serverRootPath,
|
|
479
|
-
"publisher.config.json",
|
|
480
|
-
);
|
|
481
|
-
writeFileSync(publisherConfigPath, "invalid json");
|
|
482
|
-
|
|
483
|
-
// Create a new project store that will read the invalid config
|
|
484
|
-
const newProjectStore = new ProjectStore(serverRootPath);
|
|
485
|
-
|
|
486
|
-
// Test that the project store handles invalid JSON gracefully by falling back to empty config
|
|
487
|
-
await newProjectStore.finishedInitialization;
|
|
488
|
-
const projects = await newProjectStore.listProjects();
|
|
489
|
-
expect(projects).toEqual([]);
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
it("should handle invalid field names in publisher config without crashing", async () => {
|
|
493
|
-
// Create publisher config with invalid field names (ramen instead of name, papa instead of packages)
|
|
494
|
-
const publisherConfigPath = path.join(
|
|
495
|
-
serverRootPath,
|
|
496
|
-
"publisher.config.json",
|
|
497
|
-
);
|
|
498
|
-
writeFileSync(
|
|
499
|
-
publisherConfigPath,
|
|
500
|
-
JSON.stringify({
|
|
501
|
-
frozenConfig: false,
|
|
502
|
-
projects: [
|
|
503
|
-
{
|
|
504
|
-
invalidKey1: "malloy-samples", // Invalid: should be "name"
|
|
505
|
-
invalidKey2: [
|
|
506
|
-
// Invalid: should be "packages"
|
|
507
|
-
{
|
|
508
|
-
name: "ecommerce",
|
|
509
|
-
location:
|
|
510
|
-
"https://github.com/credibledata/malloy-samples/tree/main/ecommerce",
|
|
511
|
-
},
|
|
512
|
-
],
|
|
513
|
-
connections: [
|
|
514
|
-
{
|
|
515
|
-
name: "bigquery",
|
|
516
|
-
type: "bigquery",
|
|
517
|
-
},
|
|
518
|
-
],
|
|
519
|
-
},
|
|
520
|
-
],
|
|
521
|
-
}),
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
// Create a new project store that will read the invalid config
|
|
525
|
-
const newProjectStore = new ProjectStore(serverRootPath);
|
|
526
|
-
|
|
527
|
-
// Test that the project store handles invalid fields gracefully without crashing
|
|
528
|
-
await newProjectStore.finishedInitialization;
|
|
529
|
-
const projects = await newProjectStore.listProjects();
|
|
530
|
-
|
|
531
|
-
// Should not crash and should return empty array since invalid projects are filtered out
|
|
532
|
-
expect(projects).toEqual([]);
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
it("should filter out invalid projects from publisher config", async () => {
|
|
536
|
-
// Create publisher config with mix of valid and invalid projects
|
|
537
|
-
const publisherConfigPath = path.join(
|
|
538
|
-
serverRootPath,
|
|
539
|
-
"publisher.config.json",
|
|
540
|
-
);
|
|
541
|
-
const validProjectPath = path.join(serverRootPath, "valid-project");
|
|
542
|
-
mkdirSync(validProjectPath, { recursive: true });
|
|
543
|
-
writeFileSync(
|
|
544
|
-
path.join(validProjectPath, "publisher.json"),
|
|
545
|
-
JSON.stringify({
|
|
546
|
-
name: "valid-project",
|
|
547
|
-
description: "Valid project",
|
|
548
|
-
}),
|
|
549
|
-
);
|
|
550
|
-
|
|
551
|
-
writeFileSync(
|
|
552
|
-
publisherConfigPath,
|
|
553
|
-
JSON.stringify({
|
|
554
|
-
frozenConfig: false,
|
|
555
|
-
projects: [
|
|
556
|
-
{
|
|
557
|
-
// Invalid project: missing "name" field
|
|
558
|
-
packages: [
|
|
559
|
-
{
|
|
560
|
-
name: "package1",
|
|
561
|
-
location: "./invalid-project",
|
|
562
|
-
},
|
|
563
|
-
],
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
// Invalid project: "invalidKey1" instead of "name"
|
|
567
|
-
invalidKey1: "invalid-project-2",
|
|
568
|
-
packages: [
|
|
569
|
-
{
|
|
570
|
-
name: "package2",
|
|
571
|
-
location: "./invalid-project-2",
|
|
572
|
-
},
|
|
573
|
-
],
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
// Invalid project: "invalidKey2" instead of "packages"
|
|
577
|
-
name: "invalid-project-3",
|
|
578
|
-
invalidKey2: [
|
|
579
|
-
{
|
|
580
|
-
name: "package3",
|
|
581
|
-
location: "./invalid-project-3",
|
|
582
|
-
},
|
|
583
|
-
],
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
// Valid project
|
|
587
|
-
name: "valid-project",
|
|
588
|
-
packages: [
|
|
589
|
-
{
|
|
590
|
-
name: "valid-project",
|
|
591
|
-
location: "./valid-project",
|
|
592
|
-
},
|
|
593
|
-
],
|
|
594
|
-
},
|
|
595
|
-
],
|
|
596
|
-
}),
|
|
597
|
-
);
|
|
598
|
-
|
|
599
|
-
// Create a new project store that will read the config
|
|
600
|
-
const newProjectStore = new ProjectStore(serverRootPath);
|
|
601
|
-
|
|
602
|
-
// Test that invalid projects are filtered out
|
|
603
|
-
await newProjectStore.finishedInitialization;
|
|
604
|
-
const projects = await newProjectStore.listProjects();
|
|
605
|
-
|
|
606
|
-
// Should only have the valid project
|
|
607
|
-
expect(projects.length).toBe(1);
|
|
608
|
-
expect(projects[0].name).toBe("valid-project");
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
it(
|
|
612
|
-
"should handle concurrent project access",
|
|
613
|
-
async () => {
|
|
614
|
-
// Create a project directory
|
|
615
|
-
const projectPath = path.join(serverRootPath, projectName);
|
|
616
|
-
mkdirSync(projectPath, { recursive: true });
|
|
617
|
-
// Create publisher.json manifest file
|
|
618
|
-
writeFileSync(
|
|
619
|
-
path.join(projectPath, "publisher.json"),
|
|
620
|
-
JSON.stringify({
|
|
621
|
-
name: projectName,
|
|
622
|
-
description: "Test package",
|
|
623
|
-
}),
|
|
624
|
-
);
|
|
625
|
-
|
|
626
|
-
const publisherConfigPath = path.join(
|
|
627
|
-
serverRootPath,
|
|
628
|
-
"publisher.config.json",
|
|
629
|
-
);
|
|
630
|
-
writeFileSync(
|
|
631
|
-
publisherConfigPath,
|
|
632
|
-
JSON.stringify({
|
|
633
|
-
frozenConfig: false,
|
|
634
|
-
projects: [
|
|
635
|
-
{
|
|
636
|
-
name: projectName,
|
|
637
|
-
packages: [
|
|
638
|
-
{
|
|
639
|
-
name: projectName,
|
|
640
|
-
location: projectPath,
|
|
641
|
-
},
|
|
642
|
-
],
|
|
643
|
-
connections: [
|
|
644
|
-
{
|
|
645
|
-
name: "testConnection",
|
|
646
|
-
type: "postgres",
|
|
647
|
-
},
|
|
648
|
-
],
|
|
649
|
-
},
|
|
650
|
-
],
|
|
651
|
-
}),
|
|
652
|
-
);
|
|
653
|
-
|
|
654
|
-
await projectStore.finishedInitialization;
|
|
655
|
-
|
|
656
|
-
// Test concurrent access to the same project
|
|
657
|
-
const promises = Array.from({ length: 5 }, () =>
|
|
658
|
-
projectStore.getProject(projectName),
|
|
659
|
-
);
|
|
660
|
-
|
|
661
|
-
const projects = await Promise.all(promises);
|
|
662
|
-
|
|
663
|
-
expect(projects).toHaveLength(5);
|
|
664
|
-
projects.forEach((project) => {
|
|
665
|
-
expect(project).toBeInstanceOf(Project);
|
|
666
|
-
expect(project.metadata.name).toBe(projectName);
|
|
667
|
-
});
|
|
668
|
-
},
|
|
669
|
-
{ timeout: 30000 },
|
|
670
|
-
);
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
describe("Project Service Error Recovery", () => {
|
|
674
|
-
let sandbox: sinon.SinonSandbox;
|
|
675
|
-
let projectStore: ProjectStore;
|
|
676
|
-
const serverRootPath = path.join(
|
|
677
|
-
TEMP_DIR_PATH,
|
|
678
|
-
"pathways-worker-publisher-error-recovery-test",
|
|
679
|
-
);
|
|
680
|
-
const projectName = "organizationName-projectName-error-recovery";
|
|
681
|
-
const testConnections: Connection[] = [
|
|
682
|
-
{
|
|
683
|
-
name: "testConnection",
|
|
684
|
-
type: "postgres",
|
|
685
|
-
postgresConnection: {
|
|
686
|
-
host: "host",
|
|
687
|
-
port: 1234,
|
|
688
|
-
databaseName: "databaseName",
|
|
689
|
-
userName: "userName",
|
|
690
|
-
password: "password",
|
|
691
|
-
},
|
|
692
|
-
},
|
|
693
|
-
];
|
|
694
|
-
|
|
695
|
-
beforeEach(async () => {
|
|
696
|
-
sandbox = sinon.createSandbox();
|
|
697
|
-
mkdirSync(serverRootPath, { recursive: true });
|
|
698
|
-
|
|
699
|
-
// Mock the configuration to prevent initialization errors
|
|
700
|
-
mock(isPublisherConfigFrozen).mockReturnValue(false);
|
|
701
|
-
mock.module("../config", () => ({
|
|
702
|
-
isPublisherConfigFrozen: () => false,
|
|
703
|
-
}));
|
|
704
|
-
|
|
705
|
-
// Create project store after mocking
|
|
706
|
-
projectStore = new ProjectStore(serverRootPath);
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
afterEach(async () => {
|
|
710
|
-
sandbox.restore();
|
|
711
|
-
if (existsSync(serverRootPath)) {
|
|
712
|
-
rmSync(serverRootPath, { recursive: true, force: true });
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
describe("Project Loading Error Recovery", () => {
|
|
717
|
-
it("should handle missing project directories gracefully", async () => {
|
|
718
|
-
// Create publisher config with missing project directory
|
|
719
|
-
const publisherConfigPath = path.join(
|
|
720
|
-
serverRootPath,
|
|
721
|
-
"publisher.config.json",
|
|
722
|
-
);
|
|
723
|
-
writeFileSync(
|
|
724
|
-
publisherConfigPath,
|
|
725
|
-
JSON.stringify({
|
|
726
|
-
projects: [
|
|
727
|
-
{
|
|
728
|
-
name: projectName,
|
|
729
|
-
packages: [
|
|
730
|
-
{
|
|
731
|
-
name: projectName,
|
|
732
|
-
location: path.join(
|
|
733
|
-
serverRootPath,
|
|
734
|
-
"missing-project",
|
|
735
|
-
),
|
|
736
|
-
},
|
|
737
|
-
],
|
|
738
|
-
},
|
|
739
|
-
],
|
|
740
|
-
}),
|
|
741
|
-
);
|
|
742
|
-
|
|
743
|
-
// Test that the project store handles the missing directory
|
|
744
|
-
await expect(projectStore.getProject(projectName)).rejects.toThrow();
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
it(
|
|
748
|
-
"should handle corrupted connection files",
|
|
749
|
-
async () => {
|
|
750
|
-
// Create a project directory
|
|
751
|
-
const projectPath = path.join(serverRootPath, projectName);
|
|
752
|
-
mkdirSync(projectPath, { recursive: true });
|
|
753
|
-
// Create publisher.json manifest file
|
|
754
|
-
writeFileSync(
|
|
755
|
-
path.join(projectPath, "publisher.json"),
|
|
756
|
-
JSON.stringify({
|
|
757
|
-
name: projectName,
|
|
758
|
-
description: "Test package",
|
|
759
|
-
}),
|
|
760
|
-
);
|
|
761
|
-
|
|
762
|
-
// Create corrupted connections file
|
|
763
|
-
const connectionsPath = path.join(
|
|
764
|
-
projectPath,
|
|
765
|
-
"publisher.connections.json",
|
|
766
|
-
);
|
|
767
|
-
writeFileSync(connectionsPath, "invalid json");
|
|
768
|
-
|
|
769
|
-
// Create publisher config
|
|
770
|
-
const publisherConfigPath = path.join(
|
|
771
|
-
serverRootPath,
|
|
772
|
-
"publisher.config.json",
|
|
773
|
-
);
|
|
774
|
-
writeFileSync(
|
|
775
|
-
publisherConfigPath,
|
|
776
|
-
JSON.stringify({
|
|
777
|
-
projects: [
|
|
778
|
-
{
|
|
779
|
-
name: projectName,
|
|
780
|
-
packages: [
|
|
781
|
-
{
|
|
782
|
-
name: projectName,
|
|
783
|
-
location: projectPath,
|
|
784
|
-
},
|
|
785
|
-
],
|
|
786
|
-
},
|
|
787
|
-
],
|
|
788
|
-
}),
|
|
789
|
-
);
|
|
790
|
-
|
|
791
|
-
// Test that the project store handles corrupted connection files gracefully
|
|
792
|
-
// (The current implementation loads the project even with corrupted connection files)
|
|
793
|
-
const project = await projectStore.getProject(projectName);
|
|
794
|
-
expect(project).toBeInstanceOf(Project);
|
|
795
|
-
expect(project.metadata.name).toBe(projectName);
|
|
796
|
-
},
|
|
797
|
-
{ timeout: 30000 },
|
|
798
|
-
);
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
describe("Project Store State Management", () => {
|
|
802
|
-
it(
|
|
803
|
-
"should maintain consistent state after errors",
|
|
804
|
-
async () => {
|
|
805
|
-
// Create a valid project first
|
|
806
|
-
const projectPath = path.join(serverRootPath, projectName);
|
|
807
|
-
mkdirSync(projectPath, { recursive: true });
|
|
808
|
-
// Create publisher.json manifest file
|
|
809
|
-
writeFileSync(
|
|
810
|
-
path.join(projectPath, "publisher.json"),
|
|
811
|
-
JSON.stringify({
|
|
812
|
-
name: projectName,
|
|
813
|
-
description: "Test package",
|
|
814
|
-
}),
|
|
815
|
-
);
|
|
816
|
-
writeFileSync(
|
|
817
|
-
path.join(projectPath, "publisher.connections.json"),
|
|
818
|
-
JSON.stringify(testConnections),
|
|
819
|
-
);
|
|
820
|
-
|
|
821
|
-
const publisherConfigPath = path.join(
|
|
822
|
-
serverRootPath,
|
|
823
|
-
"publisher.config.json",
|
|
824
|
-
);
|
|
825
|
-
writeFileSync(
|
|
826
|
-
publisherConfigPath,
|
|
827
|
-
JSON.stringify({
|
|
828
|
-
projects: [
|
|
829
|
-
{
|
|
830
|
-
name: projectName,
|
|
831
|
-
packages: [
|
|
832
|
-
{
|
|
833
|
-
name: projectName,
|
|
834
|
-
location: projectPath,
|
|
835
|
-
},
|
|
836
|
-
],
|
|
837
|
-
},
|
|
838
|
-
],
|
|
839
|
-
}),
|
|
840
|
-
);
|
|
841
|
-
|
|
842
|
-
// Get the project successfully
|
|
843
|
-
const project = await projectStore.getProject(projectName);
|
|
844
|
-
expect(project).toBeInstanceOf(Project);
|
|
845
|
-
|
|
846
|
-
// Try to get a non-existent project
|
|
847
|
-
await expect(
|
|
848
|
-
projectStore.getProject("non-existent"),
|
|
849
|
-
).rejects.toThrow();
|
|
850
|
-
|
|
851
|
-
// Verify the original project is still accessible
|
|
852
|
-
const projectAgain = await projectStore.getProject(projectName);
|
|
853
|
-
expect(projectAgain).toBeInstanceOf(Project);
|
|
854
|
-
expect(projectAgain.metadata.name).toBe(projectName);
|
|
855
|
-
},
|
|
856
|
-
{ timeout: 30000 },
|
|
857
|
-
);
|
|
858
|
-
});
|
|
859
|
-
});
|