@malloy-publisher/server 0.0.196-dev → 0.0.196
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/dist/app/api-doc.yaml +213 -214
- package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
- package/dist/app/assets/HomePage-DMop21VG.js +1 -0
- package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
- package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
- package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
- package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
- package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
- package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
- package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
- package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
- package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
- package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +1954 -1318
- package/package.json +1 -1
- package/publisher.config.json +2 -2
- package/src/config.spec.ts +181 -66
- package/src/config.ts +68 -47
- package/src/controller/compile.controller.ts +10 -7
- package/src/controller/connection.controller.ts +79 -58
- package/src/controller/database.controller.ts +10 -7
- package/src/controller/manifest.controller.ts +23 -14
- package/src/controller/materialization.controller.ts +14 -14
- package/src/controller/model.controller.ts +35 -20
- package/src/controller/package.controller.ts +83 -49
- package/src/controller/query.controller.ts +11 -8
- package/src/controller/watch-mode.controller.ts +35 -29
- package/src/errors.ts +2 -2
- package/src/mcp/error_messages.ts +2 -2
- package/src/mcp/handler_utils.ts +23 -20
- package/src/mcp/mcp_constants.ts +1 -1
- package/src/mcp/prompts/handlers.ts +3 -3
- package/src/mcp/prompts/prompt_service.ts +5 -5
- package/src/mcp/prompts/utils.ts +12 -12
- package/src/mcp/resource_metadata.ts +3 -3
- package/src/mcp/resources/environment_resource.ts +187 -0
- package/src/mcp/resources/model_resource.ts +19 -17
- package/src/mcp/resources/notebook_resource.ts +13 -13
- package/src/mcp/resources/package_resource.ts +30 -27
- package/src/mcp/resources/query_resource.ts +15 -10
- package/src/mcp/resources/source_resource.ts +10 -10
- package/src/mcp/resources/view_resource.ts +11 -11
- package/src/mcp/server.ts +16 -14
- package/src/mcp/tools/discovery_tools.ts +67 -49
- package/src/mcp/tools/execute_query_tool.ts +14 -14
- package/src/server-old.ts +1119 -0
- package/src/server.ts +191 -159
- package/src/service/connection.spec.ts +158 -133
- package/src/service/connection.ts +42 -39
- package/src/service/connection_config.spec.ts +13 -11
- package/src/service/connection_config.ts +28 -19
- package/src/service/connection_service.spec.ts +63 -43
- package/src/service/connection_service.ts +106 -89
- package/src/service/{project.ts → environment.ts} +92 -77
- package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
- package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -85
- package/src/service/{project_store.ts → environment_store.ts} +368 -326
- package/src/service/manifest_service.spec.ts +15 -15
- package/src/service/manifest_service.ts +26 -21
- package/src/service/materialization_service.spec.ts +93 -59
- package/src/service/materialization_service.ts +71 -62
- package/src/service/materialized_table_gc.spec.ts +15 -15
- package/src/service/materialized_table_gc.ts +3 -3
- package/src/service/model.ts +2 -2
- package/src/service/package.spec.ts +2 -2
- package/src/service/package.ts +23 -21
- package/src/service/resolve_environment.ts +15 -0
- package/src/storage/DatabaseInterface.ts +34 -25
- package/src/storage/StorageManager.mock.ts +3 -3
- package/src/storage/StorageManager.ts +24 -23
- package/src/storage/duckdb/ConnectionRepository.ts +13 -11
- package/src/storage/duckdb/DuckDBConnection.ts +1 -1
- package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
- package/src/storage/duckdb/DuckDBRepository.ts +47 -47
- package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
- package/src/storage/duckdb/ManifestRepository.ts +21 -20
- package/src/storage/duckdb/MaterializationRepository.ts +31 -28
- package/src/storage/duckdb/PackageRepository.ts +11 -11
- package/src/storage/duckdb/manifest_store.spec.ts +2 -2
- package/src/storage/duckdb/schema.ts +61 -20
- package/src/storage/ducklake/DuckLakeManifestStore.ts +14 -14
- package/tests/fixtures/publisher.config.json +1 -1
- package/tests/harness/e2e.ts +1 -1
- package/tests/harness/mcp_test_setup.ts +1 -1
- package/tests/harness/mocks.ts +10 -8
- package/tests/harness/rest_e2e.ts +2 -2
- package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +259 -0
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +27 -48
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +26 -35
- package/tests/unit/duckdb/attached_databases.test.ts +51 -33
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +194 -0
- package/tests/unit/ducklake/ducklake.test.ts +24 -22
- package/tests/unit/mcp/prompt_happy.test.ts +8 -8
- package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
- package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
- package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
- package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
- package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
- package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/service/resolve_project.ts +0 -13
|
@@ -10,7 +10,7 @@ const __dirname = path.dirname(__filename);
|
|
|
10
10
|
|
|
11
11
|
const PROJECT_NAME = "test-project";
|
|
12
12
|
const PACKAGE_NAME = "persist-test";
|
|
13
|
-
const API = `/api/v0/
|
|
13
|
+
const API = `/api/v0/environments/${PROJECT_NAME}/packages/${PACKAGE_NAME}`;
|
|
14
14
|
|
|
15
15
|
describe("Materialization & Manifest REST API (E2E)", () => {
|
|
16
16
|
let env: (RestE2EEnv & { stop(): Promise<void> }) | null = null;
|
|
@@ -23,7 +23,7 @@ describe("Materialization & Manifest REST API (E2E)", () => {
|
|
|
23
23
|
// Create the test project via the REST API using an absolute
|
|
24
24
|
// path to the fixture so it works regardless of SERVER_ROOT.
|
|
25
25
|
const fixtureDir = path.resolve(__dirname, "../../fixtures/persist-test");
|
|
26
|
-
const createRes = await fetch(`${baseUrl}/api/v0/
|
|
26
|
+
const createRes = await fetch(`${baseUrl}/api/v0/environments`, {
|
|
27
27
|
method: "POST",
|
|
28
28
|
headers: { "Content-Type": "application/json" },
|
|
29
29
|
body: JSON.stringify({
|
|
@@ -45,7 +45,7 @@ describe("Materialization & Manifest REST API (E2E)", () => {
|
|
|
45
45
|
while (!pkgReady && Date.now() < deadline) {
|
|
46
46
|
try {
|
|
47
47
|
const res = await fetch(
|
|
48
|
-
`${baseUrl}/api/v0/
|
|
48
|
+
`${baseUrl}/api/v0/environments/${PROJECT_NAME}/packages/${PACKAGE_NAME}`,
|
|
49
49
|
);
|
|
50
50
|
if (res.ok) {
|
|
51
51
|
pkgReady = true;
|
|
@@ -65,7 +65,7 @@ describe("Materialization & Manifest REST API (E2E)", () => {
|
|
|
65
65
|
// Tear down the test project, then the HTTP server.
|
|
66
66
|
if (baseUrl) {
|
|
67
67
|
try {
|
|
68
|
-
await fetch(`${baseUrl}/api/v0/
|
|
68
|
+
await fetch(`${baseUrl}/api/v0/environments/${PROJECT_NAME}`, {
|
|
69
69
|
method: "DELETE",
|
|
70
70
|
});
|
|
71
71
|
} catch {
|
|
@@ -23,7 +23,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
23
23
|
let env: McpE2ETestEnvironment | null = null;
|
|
24
24
|
let mcpClient: Client;
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const ENVIRONMENT_NAME = "malloy-samples";
|
|
27
27
|
const PACKAGE_NAME = "faa";
|
|
28
28
|
|
|
29
29
|
beforeAll(async () => {
|
|
@@ -47,7 +47,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
47
47
|
const result = await mcpClient.callTool({
|
|
48
48
|
name: "malloy_executeQuery",
|
|
49
49
|
arguments: {
|
|
50
|
-
|
|
50
|
+
environmentName: ENVIRONMENT_NAME,
|
|
51
51
|
packageName: PACKAGE_NAME,
|
|
52
52
|
modelPath: "flights.malloy",
|
|
53
53
|
query: "run: flights->{ aggregate: c is count() }",
|
|
@@ -95,7 +95,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
95
95
|
async () => {
|
|
96
96
|
if (!env) throw new Error("Test environment not initialized");
|
|
97
97
|
const params = {
|
|
98
|
-
|
|
98
|
+
environmentName: ENVIRONMENT_NAME,
|
|
99
99
|
packageName: PACKAGE_NAME,
|
|
100
100
|
modelPath: "flights.malloy",
|
|
101
101
|
sourceName: "flights", // Added sourceName
|
|
@@ -140,7 +140,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
140
140
|
async () => {
|
|
141
141
|
if (!env) throw new Error("Test environment not initialized");
|
|
142
142
|
const params = {
|
|
143
|
-
|
|
143
|
+
environmentName: ENVIRONMENT_NAME,
|
|
144
144
|
packageName: PACKAGE_NAME,
|
|
145
145
|
modelPath: "flights.malloy",
|
|
146
146
|
query: "run: flights->{BAD SYNTAX aggregate: flight_count is count()}",
|
|
@@ -179,7 +179,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
179
179
|
it("should RESOLVE with InvalidParams for conflicting parameters (query and queryName)", async () => {
|
|
180
180
|
if (!env) throw new Error("Test environment not initialized");
|
|
181
181
|
const params = {
|
|
182
|
-
|
|
182
|
+
environmentName: ENVIRONMENT_NAME,
|
|
183
183
|
packageName: PACKAGE_NAME,
|
|
184
184
|
modelPath: "flights.malloy",
|
|
185
185
|
query: "run: flights->{aggregate: c is count()}",
|
|
@@ -207,7 +207,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
207
207
|
it("should RESOLVE with InvalidParams if required params are missing (e.g., query or queryName)", async () => {
|
|
208
208
|
if (!env) throw new Error("Test environment not initialized");
|
|
209
209
|
const params = {
|
|
210
|
-
|
|
210
|
+
environmentName: ENVIRONMENT_NAME,
|
|
211
211
|
packageName: PACKAGE_NAME,
|
|
212
212
|
modelPath: "flights.malloy",
|
|
213
213
|
// Missing query AND queryName
|
|
@@ -235,7 +235,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
235
235
|
if (!env) throw new Error("Test environment not initialized");
|
|
236
236
|
const params = {
|
|
237
237
|
// Missing modelPath
|
|
238
|
-
|
|
238
|
+
environmentName: ENVIRONMENT_NAME,
|
|
239
239
|
packageName: PACKAGE_NAME,
|
|
240
240
|
query: "run: flights->{aggregate: flight_count is count()}",
|
|
241
241
|
};
|
|
@@ -257,7 +257,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
257
257
|
it("should return application error if package not found", async () => {
|
|
258
258
|
if (!env) throw new Error("Test environment not initialized");
|
|
259
259
|
const params = {
|
|
260
|
-
|
|
260
|
+
environmentName: ENVIRONMENT_NAME,
|
|
261
261
|
packageName: "nonexistent_package", // Use a package that doesn't exist
|
|
262
262
|
modelPath: "flights.malloy",
|
|
263
263
|
query: "run: flights->{aggregate: c is count()}",
|
|
@@ -292,7 +292,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
292
292
|
expect(errorPayloadPkgNotFound.suggestions.length).toBeGreaterThan(0);
|
|
293
293
|
|
|
294
294
|
// Check the specific error message within the parsed object
|
|
295
|
-
const expectedErrorMessageNotFound = `Resource not found: package '${params.packageName}' in
|
|
295
|
+
const expectedErrorMessageNotFound = `Resource not found: package '${params.packageName}' in environment '${params.environmentName}'`;
|
|
296
296
|
expect(errorPayloadPkgNotFound.error).toEqual(
|
|
297
297
|
expectedErrorMessageNotFound,
|
|
298
298
|
);
|
|
@@ -301,7 +301,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
301
301
|
it("should return application error if model not found within package", async () => {
|
|
302
302
|
if (!env) throw new Error("Test environment not initialized");
|
|
303
303
|
const params = {
|
|
304
|
-
|
|
304
|
+
environmentName: ENVIRONMENT_NAME,
|
|
305
305
|
packageName: PACKAGE_NAME,
|
|
306
306
|
modelPath: "nonexistent_model.malloy", // Use a model that doesn't exist
|
|
307
307
|
query: "run: flights->{aggregate: c is count()}",
|
|
@@ -335,68 +335,47 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
|
|
|
335
335
|
expect(errorPayloadModel.suggestions.length).toBeGreaterThan(0);
|
|
336
336
|
|
|
337
337
|
// Check the specific error message within the parsed object
|
|
338
|
-
const expectedErrorMessageModel = `Resource not found: model '${params.modelPath}' in package '${params.packageName}' for
|
|
338
|
+
const expectedErrorMessageModel = `Resource not found: model '${params.modelPath}' in package '${params.packageName}' for environment '${params.environmentName}'`;
|
|
339
339
|
expect(errorPayloadModel.error).toEqual(expectedErrorMessageModel);
|
|
340
340
|
|
|
341
341
|
// Check for the specific model name and context in the message
|
|
342
342
|
expect(errorPayloadModel.error).toMatch(/Resource not found/i);
|
|
343
343
|
});
|
|
344
344
|
|
|
345
|
-
//
|
|
346
|
-
|
|
345
|
+
// Stateless HTTP + fast queries make true in-flight cancellation flaky
|
|
346
|
+
// (the response often completes before close wins the race). Assert the
|
|
347
|
+
// transport contract instead: a closed client cannot issue further tools.
|
|
348
|
+
it("should reject malloy_executeQuery after the MCP client is closed", async () => {
|
|
347
349
|
if (!env) throw new Error("Test environment not initialized");
|
|
348
350
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const cancelClient = new Client<Request, Notification, Result>({
|
|
352
|
-
name: "cancel-test-client",
|
|
351
|
+
const closedClient = new Client<Request, Notification, Result>({
|
|
352
|
+
name: "closed-client-test",
|
|
353
353
|
version: "1.0",
|
|
354
354
|
});
|
|
355
|
-
|
|
356
|
-
const cancelTransport = new StreamableHTTPClientTransport(
|
|
355
|
+
const transport = new StreamableHTTPClientTransport(
|
|
357
356
|
new URL(env.serverUrl + "/mcp"),
|
|
358
357
|
);
|
|
359
|
-
await
|
|
358
|
+
await closedClient.connect(transport);
|
|
359
|
+
await closedClient.close();
|
|
360
360
|
|
|
361
|
-
expect
|
|
362
|
-
|
|
363
|
-
try {
|
|
364
|
-
toolPromise = cancelClient.callTool({
|
|
361
|
+
await expect(
|
|
362
|
+
closedClient.callTool({
|
|
365
363
|
name: "malloy_executeQuery",
|
|
366
364
|
arguments: {
|
|
367
|
-
|
|
365
|
+
environmentName: ENVIRONMENT_NAME,
|
|
368
366
|
packageName: PACKAGE_NAME,
|
|
369
367
|
modelPath: "flights.malloy",
|
|
370
|
-
|
|
371
|
-
query: "run: flights->{aggregate: c is count() for 100}",
|
|
368
|
+
query: "run: flights->{aggregate: c is count()}",
|
|
372
369
|
},
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
// Give the request a moment to start on the server
|
|
376
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
377
|
-
|
|
378
|
-
// Close the client to trigger cancellation
|
|
379
|
-
await cancelClient.close();
|
|
380
|
-
|
|
381
|
-
// Await the promise - it should reject due to the closure
|
|
382
|
-
await toolPromise;
|
|
383
|
-
|
|
384
|
-
throw new Error("Promise should have rejected due to cancellation");
|
|
385
|
-
} catch (error) {
|
|
386
|
-
// Check that the error is an Error instance and the message indicates closure/cancellation
|
|
387
|
-
expect(error).toBeInstanceOf(Error);
|
|
388
|
-
expect((error as Error).message).toMatch(/cancel|closed/i);
|
|
389
|
-
} finally {
|
|
390
|
-
// Ensure the temporary client is closed even if the test failed unexpectedly
|
|
391
|
-
await cancelClient.close().catch(() => {}); // Ignore errors on final cleanup
|
|
392
|
-
}
|
|
370
|
+
}),
|
|
371
|
+
).rejects.toThrow();
|
|
393
372
|
});
|
|
394
373
|
|
|
395
374
|
// Test invalid usage - nested view called without sourceName
|
|
396
375
|
it("should return application error for nested view without sourceName", async () => {
|
|
397
376
|
if (!env) throw new Error("Test environment not initialized");
|
|
398
377
|
const params = {
|
|
399
|
-
|
|
378
|
+
environmentName: ENVIRONMENT_NAME,
|
|
400
379
|
packageName: PACKAGE_NAME,
|
|
401
380
|
modelPath: "flights.malloy",
|
|
402
381
|
queryName: "top_carriers", // Nested view, but sourceName is missing
|
|
@@ -37,29 +37,29 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
// --- Test Constants ---
|
|
40
|
-
const homeProjectUri = "malloy://
|
|
41
|
-
const faaPackageUri = "malloy://
|
|
40
|
+
const homeProjectUri = "malloy://environment/malloy-samples";
|
|
41
|
+
const faaPackageUri = "malloy://environment/malloy-samples/package/faa";
|
|
42
42
|
const flightsModelUri =
|
|
43
|
-
"malloy://
|
|
43
|
+
"malloy://environment/malloy-samples/package/faa/models/flights.malloy";
|
|
44
44
|
const FLIGHTS_SOURCE = "flights";
|
|
45
45
|
const FLIGHTS_CARRIER_QUERY = "flights_by_carrier";
|
|
46
46
|
const FLIGHTS_MONTH_VIEW = "flights_by_month";
|
|
47
47
|
const OVERVIEW_NOTEBOOK = "overview.malloynb";
|
|
48
48
|
const nonExistentPackageUri =
|
|
49
|
-
"malloy://
|
|
49
|
+
"malloy://environment/malloy-samples/package/nonexistent";
|
|
50
50
|
const nonExistentModelUri =
|
|
51
|
-
"malloy://
|
|
52
|
-
const nonExistentProjectUri = "malloy://
|
|
51
|
+
"malloy://environment/malloy-samples/package/faa/models/nonexistent.malloy";
|
|
52
|
+
const nonExistentProjectUri = "malloy://environment/invalid_project";
|
|
53
53
|
const invalidUri = "invalid://format";
|
|
54
54
|
|
|
55
|
-
const validSourceUri = `malloy://
|
|
56
|
-
const validQueryUri = `malloy://
|
|
57
|
-
const validViewUri = `malloy://
|
|
58
|
-
const validNotebookUri = `malloy://
|
|
59
|
-
const nonExistentSourceUri = `malloy://
|
|
60
|
-
const nonExistentQueryUri = `malloy://
|
|
61
|
-
const nonExistentViewUri = `malloy://
|
|
62
|
-
const nonExistentNotebookUri = `malloy://
|
|
55
|
+
const validSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}`;
|
|
56
|
+
const validQueryUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/queries/${FLIGHTS_CARRIER_QUERY}`;
|
|
57
|
+
const validViewUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/${FLIGHTS_MONTH_VIEW}`;
|
|
58
|
+
const validNotebookUri = `malloy://environment/malloy-samples/package/faa/notebooks/${OVERVIEW_NOTEBOOK}`;
|
|
59
|
+
const nonExistentSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/non_existent_source`;
|
|
60
|
+
const nonExistentQueryUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/queries/non_existent_query`;
|
|
61
|
+
const nonExistentViewUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/non_existent_view`;
|
|
62
|
+
const nonExistentNotebookUri = `malloy://environment/malloy-samples/package/faa/notebooks/non_existent.malloynb`;
|
|
63
63
|
|
|
64
64
|
describe("client.listResources", () => {
|
|
65
65
|
it(
|
|
@@ -217,7 +217,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
217
217
|
);
|
|
218
218
|
if (flightsEntry) {
|
|
219
219
|
expect(flightsEntry.uri).toBe(
|
|
220
|
-
"malloy://
|
|
220
|
+
"malloy://environment/malloy-samples/package/faa/sources/flights.malloy",
|
|
221
221
|
);
|
|
222
222
|
expect(flightsEntry.metadata).toBeDefined();
|
|
223
223
|
expect(flightsEntry.metadata!.description).toContain(
|
|
@@ -232,7 +232,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
232
232
|
);
|
|
233
233
|
if (notebookEntry) {
|
|
234
234
|
expect(notebookEntry.uri).toBe(
|
|
235
|
-
"malloy://
|
|
235
|
+
"malloy://environment/malloy-samples/package/faa/notebooks/aircraft_analysis.malloynb",
|
|
236
236
|
);
|
|
237
237
|
expect(notebookEntry.metadata).toBeDefined();
|
|
238
238
|
expect(notebookEntry.metadata!.description).toContain(
|
|
@@ -505,7 +505,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
505
505
|
// Adjust test to expect the generic "Resource not found" error, as the specific
|
|
506
506
|
// "not a notebook" detail isn't easily surfaced in the standard error format.
|
|
507
507
|
expect(errorPayload.error).toMatch(/Notebook 'overview.malloynb'/);
|
|
508
|
-
expect(errorPayload.error).toMatch(/
|
|
508
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
509
509
|
expect(errorPayload.suggestions).toBeDefined();
|
|
510
510
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
511
511
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -532,8 +532,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
532
532
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
533
533
|
// Check for the specific source name in the message
|
|
534
534
|
expect(errorPayload.error).toMatch(/Source 'non_existent_source'/);
|
|
535
|
-
|
|
536
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
535
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
537
536
|
expect(errorPayload.suggestions).toBeDefined();
|
|
538
537
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
539
538
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -558,8 +557,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
558
557
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
559
558
|
// Check for the specific query name in the message
|
|
560
559
|
expect(errorPayload.error).toMatch(/Query 'non_existent_query'/);
|
|
561
|
-
|
|
562
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
560
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
563
561
|
expect(errorPayload.suggestions).toBeDefined();
|
|
564
562
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
565
563
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -584,8 +582,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
584
582
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
585
583
|
// Check for the specific view name in the message
|
|
586
584
|
expect(errorPayload.error).toMatch(/View 'non_existent_view'/);
|
|
587
|
-
|
|
588
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
585
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
589
586
|
expect(errorPayload.suggestions).toBeDefined();
|
|
590
587
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
591
588
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -611,8 +608,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
611
608
|
// Check for the specific notebook name and context in the message
|
|
612
609
|
// Adjust test to expect the generic "Resource not found" error
|
|
613
610
|
expect(errorPayload.error).toMatch(/Notebook 'non_existent.malloynb'/);
|
|
614
|
-
|
|
615
|
-
expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
|
|
611
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
616
612
|
expect(errorPayload.suggestions).toBeDefined();
|
|
617
613
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
618
614
|
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
@@ -623,7 +619,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
623
619
|
|
|
624
620
|
it("should return structured app error when requesting view from wrong source", async () => {
|
|
625
621
|
if (!env) throw new Error("Test environment not initialized");
|
|
626
|
-
const wrongSourceUri = `malloy://
|
|
622
|
+
const wrongSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/aircraft/views/${FLIGHTS_MONTH_VIEW}`;
|
|
627
623
|
const result = await mcpClient.readResource({ uri: wrongSourceUri });
|
|
628
624
|
expect(result.isError).toBe(true);
|
|
629
625
|
expect(result.contents).toBeDefined();
|
|
@@ -633,22 +629,17 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
|
|
|
633
629
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
634
630
|
const errorPayload = JSON.parse((result.contents![0] as any).text);
|
|
635
631
|
expect(errorPayload.error).toBeDefined();
|
|
636
|
-
// Adjust expectation: The primary error should be the project not found
|
|
637
632
|
expect(errorPayload.error).toMatch(/Resource not found/i);
|
|
638
|
-
expect(errorPayload.error).toMatch(/
|
|
639
|
-
|
|
633
|
+
expect(errorPayload.error).toMatch(/View 'flights_by_month'/);
|
|
634
|
+
expect(errorPayload.error).toMatch(/source 'aircraft'/);
|
|
635
|
+
expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
|
|
640
636
|
expect(errorPayload.suggestions).toBeDefined();
|
|
641
637
|
expect(Array.isArray(errorPayload.suggestions)).toBe(true);
|
|
642
|
-
expect(errorPayload.suggestions.length).toBeGreaterThan(0);
|
|
643
|
-
|
|
644
|
-
// The suggestions come as full sentences, so we check for expected content
|
|
645
638
|
expect(errorPayload.suggestions.length).toBe(3);
|
|
646
639
|
expect(errorPayload.suggestions[0]).toContain(
|
|
647
640
|
"Verify the identifier or URI",
|
|
648
641
|
);
|
|
649
|
-
expect(errorPayload.suggestions[0]).toContain(
|
|
650
|
-
"project 'malloy-samples'",
|
|
651
|
-
);
|
|
642
|
+
expect(errorPayload.suggestions[0]).toContain("flights_by_month");
|
|
652
643
|
expect(errorPayload.suggestions[0]).toContain("is spelled correctly");
|
|
653
644
|
expect(errorPayload.suggestions[0]).toContain(
|
|
654
645
|
"Check capitalization and path separators",
|