mirror of
https://github.com/langgenius/dify.git
synced 2026-09-02 06:03:18 +08:00
187 lines
6.9 KiB
JavaScript
187 lines
6.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawn } from "node:child_process";
|
|
import { readFile } from "node:fs/promises";
|
|
import { createServer } from "node:http";
|
|
import { test } from "node:test";
|
|
|
|
const rootPackage = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
const scriptUrl = new URL("./semantic-compilation-rollout.mjs", import.meta.url);
|
|
const spaceId = "018f0d60-7a49-7cc2-9c1b-5b36f18f2c40";
|
|
const documentId = "018f0d60-7a49-7cc2-9c1b-5b36f18f2c41";
|
|
|
|
test("static rollout evidence is safe and includes migration 0043", async () => {
|
|
const result = await runScript({ SEMANTIC_ROLLOUT_MODE: "static" });
|
|
assert.equal(result.code, 0, result.stderr);
|
|
const payload = JSON.parse(result.stdout);
|
|
assert.equal(payload.mode, "static");
|
|
assert.equal(payload.staticEvidence.migrationId, "0043_semantic_generation_receipts");
|
|
});
|
|
|
|
test("mutating rollout modes require an exact space-scoped confirmation", async () => {
|
|
const result = await runScript({
|
|
SEMANTIC_ROLLOUT_DOCUMENT_IDS: documentId,
|
|
SEMANTIC_ROLLOUT_MODE: "canary",
|
|
SEMANTIC_ROLLOUT_SPACE_ID: spaceId,
|
|
});
|
|
assert.notEqual(result.code, 0);
|
|
assert.match(result.stderr, /SEMANTIC_ROLLOUT_APPLY=1 is required/u);
|
|
});
|
|
|
|
test("preflight is read-only, bounded, and reports durable state", async (context) => {
|
|
const requests = [];
|
|
const server = createServer((request, response) => {
|
|
requests.push({ method: request.method, url: request.url });
|
|
response.setHeader("content-type", "application/json");
|
|
if (request.url === "/health") {
|
|
response.end(JSON.stringify({ components: { database: true, objectStorage: true } }));
|
|
return;
|
|
}
|
|
if (request.url?.endsWith("/settings")) {
|
|
response.end(JSON.stringify({ configurationState: "active" }));
|
|
return;
|
|
}
|
|
if (request.url?.includes("/documents?")) {
|
|
response.end(JSON.stringify({ items: [{ id: documentId }] }));
|
|
return;
|
|
}
|
|
if (request.url?.includes("/background-tasks?")) {
|
|
response.end(JSON.stringify({ items: [] }));
|
|
return;
|
|
}
|
|
response.statusCode = 404;
|
|
response.end(JSON.stringify({ error: "not found" }));
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
context.after(() => new Promise((resolve) => server.close(resolve)));
|
|
const address = server.address();
|
|
assert(address && typeof address === "object");
|
|
|
|
const result = await runScript({
|
|
SEMANTIC_ROLLOUT_API_BASE: `http://127.0.0.1:${address.port}`,
|
|
SEMANTIC_ROLLOUT_MODE: "preflight",
|
|
SEMANTIC_ROLLOUT_SPACE_ID: spaceId,
|
|
});
|
|
assert.equal(result.code, 0, result.stderr);
|
|
const payload = JSON.parse(result.stdout);
|
|
assert.equal(payload.preflight.configurationState, "active");
|
|
assert.equal(payload.preflight.documentCount, 1);
|
|
assert.deepEqual(
|
|
requests.map((request) => request.method),
|
|
["GET", "GET", "GET", "GET"],
|
|
);
|
|
});
|
|
|
|
test("canary reindexes only explicit documents and verifies the published outline", async (context) => {
|
|
const requests = [];
|
|
const server = createServer(async (request, response) => {
|
|
let body = "";
|
|
for await (const chunk of request) body += chunk;
|
|
requests.push({ body, method: request.method, url: request.url });
|
|
response.setHeader("content-type", "application/json");
|
|
if (request.url === "/health") {
|
|
response.end(JSON.stringify({ components: { database: true, objectStorage: true } }));
|
|
return;
|
|
}
|
|
if (request.url?.endsWith("/settings")) {
|
|
response.end(JSON.stringify({ configurationState: "active" }));
|
|
return;
|
|
}
|
|
if (request.url?.includes("/documents?")) {
|
|
response.end(JSON.stringify({ items: [{ id: documentId }] }));
|
|
return;
|
|
}
|
|
if (request.url?.includes("/background-tasks?")) {
|
|
response.end(JSON.stringify({ items: [] }));
|
|
return;
|
|
}
|
|
if (request.method === "POST" && request.url?.endsWith("/documents/bulk/reindex")) {
|
|
response.statusCode = 202;
|
|
response.end(
|
|
JSON.stringify({
|
|
bulkJobId: "bulk-canary",
|
|
items: [
|
|
{
|
|
asset: { id: documentId },
|
|
status: "queued",
|
|
statusUrl: "/jobs/canary",
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
if (request.url === "/jobs/canary") {
|
|
response.end(JSON.stringify({ stage: "published" }));
|
|
return;
|
|
}
|
|
if (request.url?.endsWith(`/documents/${documentId}/outline`)) {
|
|
response.end(
|
|
JSON.stringify({
|
|
nodes: [{ sectionPath: ["Canary"], sourceNodeIds: ["node-1"] }],
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
response.statusCode = 404;
|
|
response.end(JSON.stringify({ error: "not found" }));
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
context.after(() => new Promise((resolve) => server.close(resolve)));
|
|
const address = server.address();
|
|
assert(address && typeof address === "object");
|
|
|
|
const result = await runScript({
|
|
SEMANTIC_ROLLOUT_API_BASE: `http://127.0.0.1:${address.port}`,
|
|
SEMANTIC_ROLLOUT_APPLY: "1",
|
|
SEMANTIC_ROLLOUT_CONFIRM: `semantic:canary:${spaceId}`,
|
|
SEMANTIC_ROLLOUT_DOCUMENT_IDS: documentId,
|
|
SEMANTIC_ROLLOUT_MAX_POLLS: "1",
|
|
SEMANTIC_ROLLOUT_MODE: "canary",
|
|
SEMANTIC_ROLLOUT_SPACE_ID: spaceId,
|
|
});
|
|
assert.equal(result.code, 0, result.stderr);
|
|
const payload = JSON.parse(result.stdout);
|
|
assert.equal(payload.result.documentsQueued, 1);
|
|
const mutation = requests.find((request) => request.method === "POST");
|
|
assert.deepEqual(JSON.parse(mutation?.body ?? "{}"), { documentIds: [documentId] });
|
|
});
|
|
|
|
test("package scripts expose explicit rollout phases and keep their tests in check", () => {
|
|
assert.equal(
|
|
rootPackage.scripts["semantic:rollout:static"],
|
|
"node scripts/semantic-compilation-rollout.mjs",
|
|
);
|
|
assert.match(
|
|
rootPackage.scripts["semantic:rollout:preflight"],
|
|
/SEMANTIC_ROLLOUT_MODE=preflight/u,
|
|
);
|
|
assert.match(rootPackage.scripts["semantic:rollout:canary"], /SEMANTIC_ROLLOUT_MODE=canary/u);
|
|
assert.match(rootPackage.scripts["semantic:rollout:backfill"], /SEMANTIC_ROLLOUT_MODE=backfill/u);
|
|
assert.match(rootPackage.scripts["semantic:rollout:rollback"], /SEMANTIC_ROLLOUT_MODE=rollback/u);
|
|
assert.equal(
|
|
rootPackage.scripts["semantic:rollout:test"],
|
|
"node --test scripts/semantic-compilation-rollout.test.mjs",
|
|
);
|
|
assert.match(rootPackage.scripts.check, /semantic:rollout:test/u);
|
|
});
|
|
|
|
function runScript(extraEnv) {
|
|
return new Promise((resolve) => {
|
|
const child = spawn(process.execPath, [scriptUrl.pathname], {
|
|
env: { ...process.env, ...extraEnv },
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
let stdout = "";
|
|
let stderr = "";
|
|
child.stdout.setEncoding("utf8");
|
|
child.stderr.setEncoding("utf8");
|
|
child.stdout.on("data", (chunk) => {
|
|
stdout += chunk;
|
|
});
|
|
child.stderr.on("data", (chunk) => {
|
|
stderr += chunk;
|
|
});
|
|
child.on("close", (code) => resolve({ code, stderr, stdout }));
|
|
});
|
|
}
|