test(authority): expand e2e roundtrip coverage

This commit is contained in:
Youzini-afk
2026-04-28 17:41:22 +08:00
parent 9cecb82617
commit 6747263eb2
5 changed files with 527 additions and 162 deletions

View File

@@ -1,5 +1,4 @@
import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import { probeAuthorityCapabilities } from "../../runtime/authority-capabilities.js";
import { AuthorityGraphStore } from "../../sync/authority-graph-store.js";
@@ -18,161 +17,28 @@ import {
createAuthorityJobAdapter,
} from "../../maintenance/authority-job-adapter.js";
import { createAuthorityBlobAdapter } from "../../maintenance/authority-blob-adapter.js";
import {
buildAuthorityE2eVectorEntries,
createAuthorityE2eContext,
createAuthorityE2eContractGraph,
createAuthorityE2eContractNode,
runAuthorityE2eStep,
} from "../helpers/authority-e2e-context.mjs";
const env = process.env;
const baseUrl = String(env.AUTHORITY_E2E_BASE_URL || "").trim();
const context = createAuthorityE2eContext({
skipMessage:
"authority-server-primary E2E skipped: set AUTHORITY_E2E_BASE_URL to run against a real Authority server",
});
if (!baseUrl) {
console.log("authority-server-primary E2E skipped: set AUTHORITY_E2E_BASE_URL to run against a real Authority server");
if (context.skip) {
console.log(context.skipMessage);
process.exit(0);
}
const resolvedBaseUrl = context.baseUrl;
const { chatId, namespace, collectionId, blobPath, fetchImpl, headerProvider, runId } = context;
const graph = createAuthorityE2eContractGraph(chatId, runId);
function parsePositiveInteger(value, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) {
const parsed = Number(value);
if (!Number.isFinite(parsed)) return fallback;
return Math.min(max, Math.max(min, Math.trunc(parsed)));
}
function parseJsonObject(value, fallback = {}) {
if (!value) return fallback;
try {
const parsed = JSON.parse(String(value));
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : fallback;
} catch {
return fallback;
}
}
function resolveBaseUrl(value) {
const normalized = String(value || "").replace(/\/+$/g, "");
if (/^https?:\/\//i.test(normalized)) return normalized;
const origin = String(env.AUTHORITY_E2E_ORIGIN || "").replace(/\/+$/g, "");
if (origin && normalized.startsWith("/")) return `${origin}${normalized}`;
throw new Error("AUTHORITY_E2E_BASE_URL must be absolute, or set AUTHORITY_E2E_ORIGIN for relative plugin paths");
}
function createHeaderProvider() {
const staticHeaders = parseJsonObject(env.AUTHORITY_E2E_HEADER_JSON, {});
const token = String(env.AUTHORITY_E2E_TOKEN || "").trim();
const cookie = String(env.AUTHORITY_E2E_COOKIE || "").trim();
return () => ({
...staticHeaders,
...(token ? { Authorization: /^Bearer\s+/i.test(token) ? token : `Bearer ${token}` } : {}),
...(cookie ? { Cookie: cookie } : {}),
});
}
function createFetchWithTimeout(timeoutMs) {
return async (url, options = {}) => {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(new Error(`Authority E2E request timeout after ${timeoutMs}ms`)), timeoutMs);
try {
return await fetch(url, {
...options,
signal: controller.signal,
});
} finally {
clearTimeout(timer);
}
};
}
function createContractNode(id, title, nowMs) {
return {
id,
type: "fact",
fields: {
title,
summary: `${title} generated by Authority server-primary E2E contract smoke`,
},
seqRange: [1, 1],
scope: {
layer: "global",
ownerType: "system",
ownerId: "authority-e2e",
bucket: "contract",
regionKey: "authority-e2e-region",
},
storySegmentId: "authority-e2e-segment",
importance: 0.8,
archived: false,
updatedAt: nowMs,
};
}
function createContractGraph(chatId, runId) {
const nowMs = Date.now();
const nodeA = createContractNode(`${runId}-node-a`, "Authority E2E Alpha", nowMs);
const nodeB = createContractNode(`${runId}-node-b`, "Authority E2E Beta", nowMs);
return {
meta: {
schemaVersion: 1,
chatId,
deviceId: "authority-e2e",
revision: 1,
lastModified: nowMs,
nodeCount: 2,
edgeCount: 1,
tombstoneCount: 0,
},
nodes: [nodeA, nodeB],
edges: [
{
id: `${runId}-edge-a-b`,
fromId: nodeA.id,
toId: nodeB.id,
relation: "related",
type: "semantic",
strength: 0.7,
updatedAt: nowMs,
},
],
tombstones: [],
state: {
lastProcessedFloor: 1,
extractionCount: 1,
},
};
}
function buildVectorEntries(graph) {
return graph.nodes.map((node, index) => ({
nodeId: node.id,
index,
hash: `${node.id}:hash`,
text: `${node.fields.title}. ${node.fields.summary}`,
}));
}
async function runStep(name, fn) {
const startedAt = Date.now();
try {
const result = await fn();
const durationMs = Date.now() - startedAt;
console.log(`authority E2E ${name}: ok (${durationMs}ms)`);
return { name, ok: true, durationMs, result };
} catch (error) {
const durationMs = Date.now() - startedAt;
console.error(`authority E2E ${name}: failed (${durationMs}ms)`);
console.error(error?.stack || error?.message || String(error));
throw error;
}
}
const resolvedBaseUrl = resolveBaseUrl(baseUrl);
const timeoutMs = parsePositiveInteger(env.AUTHORITY_E2E_TIMEOUT_MS, 15000, 1000, 300000);
const jobWaitTimeoutMs = parsePositiveInteger(env.AUTHORITY_E2E_JOB_WAIT_TIMEOUT_MS, 15000, 1000, 300000);
const headerProvider = createHeaderProvider();
const fetchImpl = createFetchWithTimeout(timeoutMs);
const runId = String(env.AUTHORITY_E2E_RUN_ID || `authority-e2e-${Date.now()}-${randomUUID().slice(0, 8)}`)
.replace(/[^A-Za-z0-9._:-]+/g, "-");
const chatId = String(env.AUTHORITY_E2E_CHAT_ID || `st-bme-${runId}`);
const namespace = String(env.AUTHORITY_E2E_NAMESPACE || `st-bme-e2e-${runId}`);
const collectionId = String(env.AUTHORITY_E2E_COLLECTION_ID || `${namespace}::${chatId}`);
const blobPath = String(env.AUTHORITY_E2E_BLOB_PATH || `st-bme/e2e/${runId}/contract.json`);
const graph = createContractGraph(chatId, runId);
const context = {
const runContext = {
baseUrl: resolvedBaseUrl,
chatId,
namespace,
@@ -180,9 +46,9 @@ const context = {
blobPath,
};
console.log(`authority-server-primary E2E started: ${JSON.stringify(context)}`);
console.log(`authority-server-primary E2E started: ${JSON.stringify(runContext)}`);
await runStep("probe", async () => {
await runAuthorityE2eStep("probe", async () => {
const state = await probeAuthorityCapabilities({
settings: { authorityBaseUrl: resolvedBaseUrl },
fetchImpl,
@@ -198,7 +64,7 @@ await runStep("probe", async () => {
};
});
await runStep("sql", async () => {
await runAuthorityE2eStep("sql", async () => {
const store = new AuthorityGraphStore(chatId, {
baseUrl: resolvedBaseUrl,
fetchImpl,
@@ -216,7 +82,7 @@ await runStep("sql", async () => {
const commitResult = await store.commitDelta(
{
upsertNodes: [createContractNode(`${runId}-node-c`, "Authority E2E Gamma", Date.now())],
upsertNodes: [createAuthorityE2eContractNode(`${runId}-node-c`, "Authority E2E Gamma", Date.now())],
runtimeMetaPatch: { authorityE2eRunId: runId },
},
{ reason: "authority-e2e-contract", markSyncDirty: false },
@@ -238,9 +104,9 @@ await runStep("sql", async () => {
}
});
await runStep("trivium", async () => {
await runAuthorityE2eStep("trivium", async () => {
const config = normalizeAuthorityVectorConfig({ authorityBaseUrl: resolvedBaseUrl });
const entries = buildVectorEntries(graph);
const entries = buildAuthorityE2eVectorEntries(graph);
await purgeAuthorityTriviumNamespace(config, {
namespace,
collectionId,
@@ -328,12 +194,12 @@ await runStep("trivium", async () => {
}
});
await runStep("jobs", async () => {
await runAuthorityE2eStep("jobs", async () => {
const adapter = createAuthorityJobAdapter(
{
authorityBaseUrl: resolvedBaseUrl,
pollIntervalMs: 500,
waitTimeoutMs: jobWaitTimeoutMs,
waitTimeoutMs: context.jobWaitTimeoutMs,
},
{
fetchImpl,
@@ -343,7 +209,7 @@ await runStep("jobs", async () => {
const listBefore = await adapter.listPage({ limit: 5 });
assert.ok(Array.isArray(listBefore.jobs));
const kind = String(env.AUTHORITY_E2E_JOB_KIND || "authority.vector.rebuild");
const kind = String(context.env.AUTHORITY_E2E_JOB_KIND || "authority.vector.rebuild");
const idempotencyKey = buildAuthorityJobIdempotencyKey({
kind,
chatId,
@@ -368,7 +234,7 @@ await runStep("jobs", async () => {
assert.ok(submitted.id);
const waited = await adapter.waitForCompletion(submitted.id, {
timeoutMs: jobWaitTimeoutMs,
timeoutMs: context.jobWaitTimeoutMs,
pollIntervalMs: 500,
});
assert.ok(waited.id || waited.status);
@@ -387,7 +253,7 @@ await runStep("jobs", async () => {
};
});
await runStep("blob", async () => {
await runAuthorityE2eStep("blob", async () => {
const adapter = createAuthorityBlobAdapter(
{ authorityBaseUrl: resolvedBaseUrl, authorityBlobNamespace: namespace },
{ fetchImpl, headerProvider },