@malloy-publisher/server 0.0.207 → 0.0.209
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 +84 -66
- package/dist/app/assets/{EnvironmentPage-BScgHmkw.js → EnvironmentPage-BRMCY9d8.js} +1 -1
- package/dist/app/assets/HomePage-DQzgkiMI.js +1 -0
- package/dist/app/assets/{MainPage-DWfF4jXW.js → MainPage-sZdUjUcu.js} +2 -2
- package/dist/app/assets/MaterializationsPage-C_jQQUCJ.js +1 -0
- package/dist/app/assets/{ModelPage-BiNOgK_e.js → ModelPage-Bh62OIEq.js} +1 -1
- package/dist/app/assets/{PackagePage-DAN5V7gu.js → PackagePage-DJW4xjLp.js} +1 -1
- package/dist/app/assets/{RouteError-CEnIzuKN.js → RouteError-Vi5yGs_F.js} +1 -1
- package/dist/app/assets/{WorkbookPage-gA1ceqHP.js → WorkbookPage-BvJMi21d.js} +1 -1
- package/dist/app/assets/{core-AOmIKwkc.es-Dclu1Fga.js → core-BbW0t3RQ.es-L-mZcOk3.js} +1 -1
- package/dist/app/assets/index-CCcHdeew.js +1760 -0
- package/dist/app/assets/index-CNFX-CGL.js +40 -0
- package/dist/app/assets/{index-DtlPzNxc.js → index-DuqTjxM_.js} +114 -114
- package/dist/app/assets/index.umd-GgEb4WfT.js +2467 -0
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +26182 -16839
- package/package.json +14 -11
- package/src/controller/materialization.controller.spec.ts +125 -0
- package/src/controller/materialization.controller.ts +23 -27
- package/src/health.ts +6 -0
- package/src/materialization_metrics.ts +117 -34
- package/src/mcp/agent_server.protocol.spec.ts +78 -0
- package/src/mcp/agent_server.spec.ts +18 -0
- package/src/mcp/agent_server.ts +144 -0
- package/src/mcp/server.ts +3 -0
- package/src/mcp/skills/build_skills_bundle.spec.ts +51 -0
- package/src/mcp/skills/build_skills_bundle.ts +66 -0
- package/src/mcp/skills/skills_bundle.json +1 -0
- package/src/mcp/skills/skills_bundle.spec.ts +33 -0
- package/src/mcp/tools/docs_search/build_docs_index.ts +132 -0
- package/src/mcp/tools/docs_search/malloy_docs_index.json +1 -0
- package/src/mcp/tools/docs_search_tool.spec.ts +32 -0
- package/src/mcp/tools/docs_search_tool.ts +138 -0
- package/src/mcp/tools/get_context_eval.ts +126 -0
- package/src/mcp/tools/get_context_tool.spec.ts +44 -0
- package/src/mcp/tools/get_context_tool.ts +341 -0
- package/src/server-old.ts +2 -11
- package/src/server.ts +16 -10
- package/src/service/build_plan.spec.ts +116 -0
- package/src/service/build_plan.ts +238 -0
- package/src/service/connection.ts +4 -0
- package/src/service/connection_config.spec.ts +182 -1
- package/src/service/connection_config.ts +70 -0
- package/src/service/db_utils.spec.ts +159 -1
- package/src/service/db_utils.ts +131 -0
- package/src/service/materialization_service.spec.ts +388 -184
- package/src/service/materialization_service.ts +156 -442
- package/src/service/materialization_test_fixtures.ts +119 -0
- package/src/service/package.ts +41 -1
- package/src/storage/DatabaseInterface.ts +5 -13
- package/src/storage/duckdb/MaterializationRepository.ts +5 -14
- package/src/storage/duckdb/schema.ts +4 -5
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +72 -211
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +6 -0
- package/dist/app/assets/HomePage-CGedji_w.js +0 -1
- package/dist/app/assets/MaterializationsPage-B9PDlk8c.js +0 -1
- package/dist/app/assets/index-DGGe8UpP.js +0 -40
- package/dist/app/assets/index-uu6UpHd2.js +0 -1812
- package/dist/app/assets/index.umd-DDq93YX4.js +0 -2469
|
@@ -12,16 +12,9 @@ const PROJECT_NAME = "test-project";
|
|
|
12
12
|
const PACKAGE_NAME = "persist-test";
|
|
13
13
|
const API = `/api/v0/environments/${PROJECT_NAME}/packages/${PACKAGE_NAME}`;
|
|
14
14
|
|
|
15
|
-
/** Statuses from which no background round is in flight. */
|
|
16
|
-
const SETTLED_STATUSES = [
|
|
17
|
-
"BUILD_PLAN_READY",
|
|
18
|
-
"MANIFEST_FILE_READY",
|
|
19
|
-
"FAILED",
|
|
20
|
-
"CANCELLED",
|
|
21
|
-
];
|
|
22
15
|
const TERMINAL_STATUSES = ["MANIFEST_FILE_READY", "FAILED", "CANCELLED"];
|
|
23
16
|
|
|
24
|
-
describe("Materialization REST API:
|
|
17
|
+
describe("Materialization REST API: single-call (E2E)", () => {
|
|
25
18
|
let env: (RestE2EEnv & { stop(): Promise<void> }) | null = null;
|
|
26
19
|
let baseUrl: string;
|
|
27
20
|
|
|
@@ -91,16 +84,13 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
91
84
|
return `${baseUrl}${API}${p}`;
|
|
92
85
|
}
|
|
93
86
|
|
|
94
|
-
// Most tests here exercise the control-plane two-round flow, so default to
|
|
95
|
-
// pauseBetweenPhases=true (pause at BUILD_PLAN_READY). The auto-run group
|
|
96
|
-
// overrides this with pauseBetweenPhases=false.
|
|
97
87
|
async function createMaterialization(
|
|
98
88
|
body: Record<string, unknown> = {},
|
|
99
89
|
): Promise<Response> {
|
|
100
90
|
return fetch(url("/materializations"), {
|
|
101
91
|
method: "POST",
|
|
102
92
|
headers: { "Content-Type": "application/json" },
|
|
103
|
-
body: JSON.stringify(
|
|
93
|
+
body: JSON.stringify(body),
|
|
104
94
|
});
|
|
105
95
|
}
|
|
106
96
|
|
|
@@ -129,10 +119,8 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
129
119
|
throw new Error(`Materialization ${id} did not reach the expected state`);
|
|
130
120
|
}
|
|
131
121
|
|
|
132
|
-
const
|
|
133
|
-
pollUntil(id, (s) =>
|
|
134
|
-
const pollUntilTerminal = (id: string) =>
|
|
135
|
-
pollUntil(id, (s) => TERMINAL_STATUSES.includes(s));
|
|
122
|
+
const pollUntilTerminal = (id: string, timeoutMs = 90_000) =>
|
|
123
|
+
pollUntil(id, (s) => TERMINAL_STATUSES.includes(s), timeoutMs);
|
|
136
124
|
|
|
137
125
|
/**
|
|
138
126
|
* Drive a materialization to a terminal state and delete its record so it
|
|
@@ -142,9 +130,8 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
142
130
|
const res = await fetch(url(`/materializations/${id}`));
|
|
143
131
|
if (res.status !== 200) return;
|
|
144
132
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!TERMINAL_STATUSES.includes(settled.status as string)) {
|
|
133
|
+
const current = (await res.json()) as Record<string, unknown>;
|
|
134
|
+
if (!TERMINAL_STATUSES.includes(current.status as string)) {
|
|
148
135
|
await fetch(url(`/materializations/${id}?action=stop`), {
|
|
149
136
|
method: "POST",
|
|
150
137
|
});
|
|
@@ -153,11 +140,12 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
153
140
|
await fetch(url(`/materializations/${id}`), { method: "DELETE" });
|
|
154
141
|
}
|
|
155
142
|
|
|
156
|
-
/**
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
143
|
+
/** Read Package.buildPlan and return its first planned source. */
|
|
144
|
+
async function firstPlanSource(): Promise<Record<string, unknown>> {
|
|
145
|
+
const res = await fetch(url(""));
|
|
146
|
+
expect(res.status).toBe(200);
|
|
147
|
+
const pkg = (await res.json()) as Record<string, unknown>;
|
|
148
|
+
const plan = pkg.buildPlan as Record<string, unknown>;
|
|
161
149
|
expect(plan).toBeDefined();
|
|
162
150
|
const sources = plan.sources as Record<string, Record<string, unknown>>;
|
|
163
151
|
const values = Object.values(sources);
|
|
@@ -165,29 +153,22 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
165
153
|
return values[0];
|
|
166
154
|
}
|
|
167
155
|
|
|
168
|
-
// ── Group
|
|
156
|
+
// ── Group A: Auto-run lifecycle (default) ────────────────────────
|
|
169
157
|
|
|
170
158
|
describe("auto-run lifecycle (default)", () => {
|
|
171
159
|
it(
|
|
172
160
|
"runs all phases on create, self-assigns names, and auto-loads",
|
|
173
161
|
async () => {
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
const createRes = await createMaterialization(
|
|
177
|
-
pauseBetweenPhases: false,
|
|
178
|
-
});
|
|
162
|
+
// No buildInstructions: the publisher compiles, self-assigns names,
|
|
163
|
+
// builds every persist source, and auto-loads in one pass.
|
|
164
|
+
const createRes = await createMaterialization();
|
|
179
165
|
expect(createRes.status).toBe(201);
|
|
180
166
|
const created = (await createRes.json()) as Record<string, unknown>;
|
|
181
167
|
expect(created.status).toBe("PENDING");
|
|
182
|
-
expect(created.pauseBetweenPhases).toBe(false);
|
|
183
168
|
const id = created.id as string;
|
|
184
169
|
|
|
185
170
|
// It settles at MANIFEST_FILE_READY without any build instruction.
|
|
186
|
-
const built = await
|
|
187
|
-
id,
|
|
188
|
-
(s) => TERMINAL_STATUSES.includes(s),
|
|
189
|
-
90_000,
|
|
190
|
-
);
|
|
171
|
+
const built = await pollUntilTerminal(id);
|
|
191
172
|
expect(built.status).toBe("MANIFEST_FILE_READY");
|
|
192
173
|
|
|
193
174
|
// The manifest carries the self-assigned physical table name (from
|
|
@@ -213,59 +194,37 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
213
194
|
},
|
|
214
195
|
{ timeout: 120_000 },
|
|
215
196
|
);
|
|
197
|
+
});
|
|
216
198
|
|
|
217
|
-
|
|
218
|
-
// Force a pause so we can observe a BUILD_PLAN_READY record, but mark it
|
|
219
|
-
// auto-run via metadata by creating a real auto-run and racing build is
|
|
220
|
-
// flaky; instead assert the guard on a freshly created auto-run that we
|
|
221
|
-
// immediately try to build. Auto-run never needs Round 2.
|
|
222
|
-
const createRes = await createMaterialization({
|
|
223
|
-
pauseBetweenPhases: false,
|
|
224
|
-
});
|
|
225
|
-
expect(createRes.status).toBe(201);
|
|
226
|
-
const { id } = (await createRes.json()) as { id: string };
|
|
199
|
+
// ── Group B: Orchestrated single-call build ──────────────────────
|
|
227
200
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
201
|
+
describe("orchestrated build (buildInstructions)", () => {
|
|
202
|
+
it(
|
|
203
|
+
"builds directly into caller-assigned names from Package.buildPlan",
|
|
204
|
+
async () => {
|
|
205
|
+
// Read the plan off the package, derive one caller-assigned
|
|
206
|
+
// instruction, and create the materialization already building it.
|
|
207
|
+
const source = await firstPlanSource();
|
|
208
|
+
expect(source.name).toBe("order_summary");
|
|
209
|
+
expect(typeof source.buildId).toBe("string");
|
|
231
210
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
method: "POST",
|
|
236
|
-
headers: { "Content-Type": "application/json" },
|
|
237
|
-
body: JSON.stringify({
|
|
211
|
+
const physicalTableName = "order_summary_built";
|
|
212
|
+
const createRes = await createMaterialization({
|
|
213
|
+
buildInstructions: {
|
|
238
214
|
sources: [
|
|
239
215
|
{
|
|
240
|
-
buildId:
|
|
241
|
-
|
|
242
|
-
|
|
216
|
+
buildId: source.buildId,
|
|
217
|
+
sourceID: source.sourceID,
|
|
218
|
+
materializedTableId: "mt-order-summary",
|
|
219
|
+
physicalTableName,
|
|
243
220
|
realization: "COPY",
|
|
244
221
|
},
|
|
245
222
|
],
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
);
|
|
249
|
-
expect(buildRes.status).toBe(409);
|
|
250
|
-
|
|
251
|
-
await fetch(url(`/materializations/${id}?dropTables=true`), {
|
|
252
|
-
method: "DELETE",
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// ── Group A: Full two-round lifecycle (happy path) ────────────────
|
|
258
|
-
|
|
259
|
-
describe("full two-round lifecycle", () => {
|
|
260
|
-
it(
|
|
261
|
-
"plans (round 1), builds on control-plane instruction (round 2), then deletes",
|
|
262
|
-
async () => {
|
|
263
|
-
// Round 1: create kicks off compile + plan in the background.
|
|
264
|
-
const createRes = await createMaterialization();
|
|
223
|
+
},
|
|
224
|
+
});
|
|
265
225
|
expect(createRes.status).toBe(201);
|
|
266
226
|
const created = (await createRes.json()) as Record<string, unknown>;
|
|
267
227
|
expect(created.status).toBe("PENDING");
|
|
268
|
-
expect(created.id).toBeDefined();
|
|
269
228
|
const id = created.id as string;
|
|
270
229
|
|
|
271
230
|
// List should include the new run.
|
|
@@ -274,49 +233,10 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
274
233
|
const list = (await listRes.json()) as Record<string, unknown>[];
|
|
275
234
|
expect(list.some((m) => m.id === id)).toBe(true);
|
|
276
235
|
|
|
277
|
-
//
|
|
278
|
-
const
|
|
279
|
-
id,
|
|
280
|
-
(s) => s === "BUILD_PLAN_READY" || TERMINAL_STATUSES.includes(s),
|
|
281
|
-
);
|
|
282
|
-
expect(planned.status).toBe("BUILD_PLAN_READY");
|
|
283
|
-
const source = firstPlannedSource(planned);
|
|
284
|
-
expect(source.name).toBe("order_summary");
|
|
285
|
-
expect(typeof source.buildId).toBe("string");
|
|
286
|
-
|
|
287
|
-
// Round 2: control plane instructs a COPY build into a physical name.
|
|
288
|
-
const physicalTableName = "order_summary_built";
|
|
289
|
-
const buildRes = await fetch(
|
|
290
|
-
url(`/materializations/${id}?action=build`),
|
|
291
|
-
{
|
|
292
|
-
method: "POST",
|
|
293
|
-
headers: { "Content-Type": "application/json" },
|
|
294
|
-
body: JSON.stringify({
|
|
295
|
-
sources: [
|
|
296
|
-
{
|
|
297
|
-
buildId: source.buildId,
|
|
298
|
-
sourceID: source.sourceID,
|
|
299
|
-
materializedTableId: "mt-order-summary",
|
|
300
|
-
physicalTableName,
|
|
301
|
-
realization: "COPY",
|
|
302
|
-
},
|
|
303
|
-
],
|
|
304
|
-
}),
|
|
305
|
-
},
|
|
306
|
-
);
|
|
307
|
-
expect(buildRes.status).toBe(202);
|
|
308
|
-
|
|
309
|
-
// Round 2 completes at MANIFEST_FILE_READY with an inline manifest.
|
|
310
|
-
const built = await pollUntil(
|
|
311
|
-
id,
|
|
312
|
-
(s) =>
|
|
313
|
-
s === "MANIFEST_FILE_READY" ||
|
|
314
|
-
s === "FAILED" ||
|
|
315
|
-
s === "CANCELLED",
|
|
316
|
-
);
|
|
236
|
+
// Settles at MANIFEST_FILE_READY with the caller-assigned name.
|
|
237
|
+
const built = await pollUntilTerminal(id);
|
|
317
238
|
expect(built.status).toBe("MANIFEST_FILE_READY");
|
|
318
239
|
const manifest = built.manifest as Record<string, unknown>;
|
|
319
|
-
expect(manifest).toBeDefined();
|
|
320
240
|
const entries = manifest.entries as Record<
|
|
321
241
|
string,
|
|
322
242
|
Record<string, unknown>
|
|
@@ -327,7 +247,7 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
327
247
|
expect(entry.sourceName).toBe("order_summary");
|
|
328
248
|
|
|
329
249
|
// A terminal materialization can be deleted; dropTables=true also
|
|
330
|
-
// drops the physical table this run produced
|
|
250
|
+
// drops the physical table this run produced.
|
|
331
251
|
const deleteRes = await fetch(
|
|
332
252
|
url(`/materializations/${id}?dropTables=true`),
|
|
333
253
|
{ method: "DELETE" },
|
|
@@ -340,9 +260,25 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
340
260
|
},
|
|
341
261
|
{ timeout: 90_000 },
|
|
342
262
|
);
|
|
263
|
+
|
|
264
|
+
it("rejects buildInstructions with an unknown buildId at create (400)", async () => {
|
|
265
|
+
const createRes = await createMaterialization({
|
|
266
|
+
buildInstructions: {
|
|
267
|
+
sources: [
|
|
268
|
+
{
|
|
269
|
+
buildId: "not-a-real-build-id",
|
|
270
|
+
materializedTableId: "mt",
|
|
271
|
+
physicalTableName: "t",
|
|
272
|
+
realization: "COPY",
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
expect(createRes.status).toBe(400);
|
|
278
|
+
});
|
|
343
279
|
});
|
|
344
280
|
|
|
345
|
-
// ── Group
|
|
281
|
+
// ── Group C: Serve-time routing (the payoff) ─────────────────────
|
|
346
282
|
//
|
|
347
283
|
// Materialization only pays off if *served* queries scan the materialized
|
|
348
284
|
// table instead of recomputing from the base table. The auto-run path builds
|
|
@@ -350,8 +286,7 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
350
286
|
// process, so routing is provable in-process: capture the live SQL (scans the
|
|
351
287
|
// base CSV), run an auto-run materialization, then assert both the executed
|
|
352
288
|
// query SQL and the /compile preview SQL now scan the physical table and no
|
|
353
|
-
// longer touch the base CSV.
|
|
354
|
-
// (Environment.compileSource threads the package's bound manifest).
|
|
289
|
+
// longer touch the base CSV.
|
|
355
290
|
describe("serve-time routing (auto-load)", () => {
|
|
356
291
|
const MODEL_PATH = "persist_test.malloy";
|
|
357
292
|
const QUERY = "run: order_summary -> { aggregate: c is count() }";
|
|
@@ -390,8 +325,6 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
390
325
|
"routes served + compiled queries to the materialized table after auto-load",
|
|
391
326
|
async () => {
|
|
392
327
|
// Reset to live: a prior group may have left an in-memory binding.
|
|
393
|
-
// Auto-load never writes manifestLocation to publisher.json, so a
|
|
394
|
-
// reload re-reads the live model.
|
|
395
328
|
await fetch(`${baseUrl}${API}?reload=true`);
|
|
396
329
|
|
|
397
330
|
// Baseline: with nothing materialized, both paths recompute from the
|
|
@@ -401,16 +334,10 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
401
334
|
|
|
402
335
|
// Build + auto-load in one pass (self-assigns physicalTableName =
|
|
403
336
|
// "order_summary" from `#@ persist name="order_summary"`).
|
|
404
|
-
const createRes = await createMaterialization(
|
|
405
|
-
pauseBetweenPhases: false,
|
|
406
|
-
});
|
|
337
|
+
const createRes = await createMaterialization();
|
|
407
338
|
expect(createRes.status).toBe(201);
|
|
408
339
|
const { id } = (await createRes.json()) as { id: string };
|
|
409
|
-
const built = await
|
|
410
|
-
id,
|
|
411
|
-
(s) => TERMINAL_STATUSES.includes(s),
|
|
412
|
-
90_000,
|
|
413
|
-
);
|
|
340
|
+
const built = await pollUntilTerminal(id);
|
|
414
341
|
expect(built.status).toBe("MANIFEST_FILE_READY");
|
|
415
342
|
|
|
416
343
|
// The payoff: the served query now scans the materialized table and
|
|
@@ -436,25 +363,26 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
436
363
|
);
|
|
437
364
|
});
|
|
438
365
|
|
|
439
|
-
// ── Group
|
|
366
|
+
// ── Group D: State machine and error cases ───────────────────────
|
|
440
367
|
|
|
441
368
|
describe("state machine and errors", () => {
|
|
442
|
-
it("stops
|
|
369
|
+
it("stops an in-flight materialization (-> CANCELLED)", async () => {
|
|
370
|
+
// Create and immediately stop while the background build is still
|
|
371
|
+
// starting (PENDING). Cooperative abort drives it to CANCELLED.
|
|
443
372
|
const createRes = await createMaterialization();
|
|
444
373
|
expect(createRes.status).toBe(201);
|
|
445
374
|
const { id } = (await createRes.json()) as { id: string };
|
|
446
375
|
|
|
447
|
-
await pollUntil(id, (s) => s === "BUILD_PLAN_READY");
|
|
448
|
-
|
|
449
376
|
const stopRes = await fetch(
|
|
450
377
|
url(`/materializations/${id}?action=stop`),
|
|
451
|
-
{
|
|
452
|
-
method: "POST",
|
|
453
|
-
},
|
|
378
|
+
{ method: "POST" },
|
|
454
379
|
);
|
|
455
380
|
expect(stopRes.status).toBe(200);
|
|
456
|
-
|
|
457
|
-
|
|
381
|
+
|
|
382
|
+
const settled = await pollUntilTerminal(id);
|
|
383
|
+
// The build may occasionally win the race and complete; either way it
|
|
384
|
+
// reaches a terminal state and stop returned 200.
|
|
385
|
+
expect(TERMINAL_STATUSES).toContain(settled.status as string);
|
|
458
386
|
|
|
459
387
|
await cleanup(id);
|
|
460
388
|
});
|
|
@@ -470,75 +398,12 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
470
398
|
await cleanup(id);
|
|
471
399
|
});
|
|
472
400
|
|
|
473
|
-
it("rejects building from a non-plan-ready state with 409", async () => {
|
|
474
|
-
const createRes = await createMaterialization();
|
|
475
|
-
expect(createRes.status).toBe(201);
|
|
476
|
-
const { id } = (await createRes.json()) as { id: string };
|
|
477
|
-
|
|
478
|
-
await pollUntil(id, (s) => s === "BUILD_PLAN_READY");
|
|
479
|
-
await fetch(url(`/materializations/${id}?action=stop`), {
|
|
480
|
-
method: "POST",
|
|
481
|
-
});
|
|
482
|
-
await pollUntil(id, (s) => s === "CANCELLED");
|
|
483
|
-
|
|
484
|
-
const buildRes = await fetch(
|
|
485
|
-
url(`/materializations/${id}?action=build`),
|
|
486
|
-
{
|
|
487
|
-
method: "POST",
|
|
488
|
-
headers: { "Content-Type": "application/json" },
|
|
489
|
-
body: JSON.stringify({
|
|
490
|
-
sources: [
|
|
491
|
-
{
|
|
492
|
-
buildId: "deadbeef",
|
|
493
|
-
materializedTableId: "mt",
|
|
494
|
-
physicalTableName: "t",
|
|
495
|
-
realization: "COPY",
|
|
496
|
-
},
|
|
497
|
-
],
|
|
498
|
-
}),
|
|
499
|
-
},
|
|
500
|
-
);
|
|
501
|
-
expect(buildRes.status).toBe(409);
|
|
502
|
-
|
|
503
|
-
await fetch(url(`/materializations/${id}`), { method: "DELETE" });
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it("rejects a build instruction with an unknown buildId (400)", async () => {
|
|
507
|
-
const createRes = await createMaterialization();
|
|
508
|
-
expect(createRes.status).toBe(201);
|
|
509
|
-
const { id } = (await createRes.json()) as { id: string };
|
|
510
|
-
|
|
511
|
-
await pollUntil(id, (s) => s === "BUILD_PLAN_READY");
|
|
512
|
-
|
|
513
|
-
const buildRes = await fetch(
|
|
514
|
-
url(`/materializations/${id}?action=build`),
|
|
515
|
-
{
|
|
516
|
-
method: "POST",
|
|
517
|
-
headers: { "Content-Type": "application/json" },
|
|
518
|
-
body: JSON.stringify({
|
|
519
|
-
sources: [
|
|
520
|
-
{
|
|
521
|
-
buildId: "not-a-real-build-id",
|
|
522
|
-
materializedTableId: "mt",
|
|
523
|
-
physicalTableName: "t",
|
|
524
|
-
realization: "COPY",
|
|
525
|
-
},
|
|
526
|
-
],
|
|
527
|
-
}),
|
|
528
|
-
},
|
|
529
|
-
);
|
|
530
|
-
expect(buildRes.status).toBe(400);
|
|
531
|
-
|
|
532
|
-
await cleanup(id);
|
|
533
|
-
});
|
|
534
|
-
|
|
535
401
|
it("rejects deleting a non-terminal materialization with 409", async () => {
|
|
536
402
|
const createRes = await createMaterialization();
|
|
537
403
|
expect(createRes.status).toBe(201);
|
|
538
404
|
const { id } = (await createRes.json()) as { id: string };
|
|
539
405
|
|
|
540
|
-
|
|
541
|
-
|
|
406
|
+
// Delete immediately, while the background build is still PENDING.
|
|
542
407
|
const deleteRes = await fetch(url(`/materializations/${id}`), {
|
|
543
408
|
method: "DELETE",
|
|
544
409
|
});
|
|
@@ -552,13 +417,9 @@ describe("Materialization REST API: auto-run + two-round (E2E)", () => {
|
|
|
552
417
|
expect(createRes.status).toBe(201);
|
|
553
418
|
const { id } = (await createRes.json()) as { id: string };
|
|
554
419
|
|
|
555
|
-
await pollUntil(id, (s) => s === "BUILD_PLAN_READY");
|
|
556
|
-
|
|
557
420
|
const res = await fetch(
|
|
558
421
|
url(`/materializations/${id}?action=frobnicate`),
|
|
559
|
-
{
|
|
560
|
-
method: "POST",
|
|
561
|
-
},
|
|
422
|
+
{ method: "POST" },
|
|
562
423
|
);
|
|
563
424
|
expect(res.status).toBe(400);
|
|
564
425
|
|
|
@@ -116,6 +116,11 @@ async function startServer(opts: { watch: boolean }): Promise<TestServer> {
|
|
|
116
116
|
const port = await getFreePort();
|
|
117
117
|
let mcpPort = await getFreePort();
|
|
118
118
|
while (mcpPort === port) mcpPort = await getFreePort();
|
|
119
|
+
// The agent MCP server binds its own listener; give it a distinct free port
|
|
120
|
+
// too, otherwise every spawned child binds the fixed default and collides.
|
|
121
|
+
let agentMcpPort = await getFreePort();
|
|
122
|
+
while (agentMcpPort === port || agentMcpPort === mcpPort)
|
|
123
|
+
agentMcpPort = await getFreePort();
|
|
119
124
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
120
125
|
|
|
121
126
|
const env: NodeJS.ProcessEnv = {
|
|
@@ -124,6 +129,7 @@ async function startServer(opts: { watch: boolean }): Promise<TestServer> {
|
|
|
124
129
|
PUBLISHER_HOST: "127.0.0.1",
|
|
125
130
|
PUBLISHER_PORT: String(port),
|
|
126
131
|
MCP_PORT: String(mcpPort),
|
|
132
|
+
AGENT_MCP_PORT: String(agentMcpPort),
|
|
127
133
|
};
|
|
128
134
|
if (opts.watch) env.PUBLISHER_WATCH = ENV_NAME;
|
|
129
135
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{B as n,j as t,a as o}from"./index-DtlPzNxc.js";function s(){const a=n();return t.jsx(o,{onClickEnvironment:a})}export{s as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{G as r,B as t,j as e,_ as c,a3 as o}from"./index-DtlPzNxc.js";function m(){const{environmentName:s,packageName:a}=r(),n=t();if(s)if(a){const i=c({environmentName:s,packageName:a});return e.jsx(o,{onClickPackageFile:n,resourceUri:i})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing package name"})});else return e.jsx("div",{children:e.jsx("h2",{children:"Missing environment name"})})}export{m as default};
|