feat(control-plane): add libSQL Turso backend
This commit is contained in:
@@ -5,15 +5,18 @@ const assert = require("node:assert");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const { execFileSync } = require("node:child_process");
|
||||
const { chromium } = require("playwright");
|
||||
const {
|
||||
BASE_URL,
|
||||
UI_TIMEOUT_MS,
|
||||
ensureAuthenticated,
|
||||
} = require("./tree-shell-smoke-helpers");
|
||||
const {
|
||||
setupWorkspaceAccess,
|
||||
listAiRuntimeRuns,
|
||||
listExternalConversationBindings,
|
||||
} = require("./lib/control-plane-dev-seed");
|
||||
|
||||
const CONTROL_PLANE_DB = "/mnt/Data1T/Mnote_data/control-plane/control-plane.db";
|
||||
const CHROMIUM_EXECUTABLE_PATH =
|
||||
process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH ||
|
||||
[
|
||||
@@ -79,41 +82,6 @@ function writeWorkspaceManifest(root, ownerId, workspaceId) {
|
||||
);
|
||||
}
|
||||
|
||||
function sqlQuote(value) {
|
||||
return `'${String(value).replaceAll("'", "''")}'`;
|
||||
}
|
||||
|
||||
function sqliteExec(sql) {
|
||||
execFileSync("sqlite3", [CONTROL_PLANE_DB, sql], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
}
|
||||
|
||||
function sqliteJson(sql, fallback = null) {
|
||||
try {
|
||||
const raw = execFileSync("sqlite3", ["-json", CONTROL_PLANE_DB, sql], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}).trim();
|
||||
return raw ? JSON.parse(raw) : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function grantWorkspaceAccess({ actorId, workspaceId, root, rootUri, grantId }) {
|
||||
const now = new Date().toISOString();
|
||||
sqliteExec(`
|
||||
INSERT OR IGNORE INTO users (id, email, username, display_name, role, status, created_at, updated_at, revision)
|
||||
VALUES (${sqlQuote(actorId)}, ${sqlQuote(`${actorId}@example.com`)}, ${sqlQuote(actorId)}, ${sqlQuote(actorId)}, 'user', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1);
|
||||
INSERT OR REPLACE INTO workspaces (id, owner_user_id, name, kind, root_uri, root_path, source_kind, status, created_at, updated_at, revision)
|
||||
VALUES (${sqlQuote(workspaceId)}, ${sqlQuote(actorId)}, ${sqlQuote(actorId)}, 'personal', ${sqlQuote(rootUri)}, ${sqlQuote(root)}, 'local_folder', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1);
|
||||
INSERT OR REPLACE INTO directory_grants (id, user_id, workspace_id, root_uri, root_path, permission, recursive, capabilities_json, source, status, created_by, created_at, updated_at, revision)
|
||||
VALUES (${sqlQuote(grantId)}, ${sqlQuote(actorId)}, ${sqlQuote(workspaceId)}, ${sqlQuote(rootUri)}, ${sqlQuote(root)}, 'write', 1, '["ai","markdown_edit"]', 'smoke', 'active', ${sqlQuote(actorId)}, ${sqlQuote(now)}, ${sqlQuote(now)}, 1);
|
||||
`);
|
||||
}
|
||||
|
||||
async function saveScreenshot(page, name) {
|
||||
const target = path.join(OUTPUT_DIR, `${name}.png`);
|
||||
await page.screenshot({ path: target, fullPage: false });
|
||||
@@ -152,7 +120,7 @@ async function selectProvider(page, provider) {
|
||||
);
|
||||
}
|
||||
|
||||
async function runProviderSmoke(page, provider, suffix) {
|
||||
async function runProviderSmoke(page, provider, suffix, { actorId, workspaceId }) {
|
||||
const marker = `${provider.markerPrefix}_${suffix}`;
|
||||
const runRequests = [];
|
||||
const runResponses = [];
|
||||
@@ -218,10 +186,13 @@ async function runProviderSmoke(page, provider, suffix) {
|
||||
assert.strictEqual(markerAssistantCount, 1, `${provider.key} 可见 API 回复应只有一条`);
|
||||
|
||||
const sessionId = String(run.sessionId);
|
||||
const bindingRows = sqliteJson(
|
||||
`SELECT mnote_session_id, provider, status FROM ai_external_conversation_bindings WHERE user_id=${sqlQuote("mnote-e2e")} AND mnote_session_id=${sqlQuote(sessionId)} ORDER BY updated_at DESC LIMIT 5;`,
|
||||
[],
|
||||
);
|
||||
const bindingRows = await listExternalConversationBindings(page.context().request, BASE_URL, {
|
||||
userId: actorId,
|
||||
workspaceId,
|
||||
mnoteSessionId: sessionId,
|
||||
limit: 5,
|
||||
timeoutMs: UI_TIMEOUT_MS,
|
||||
});
|
||||
assert.strictEqual(bindingRows.length, 0, `${provider.key} API ChatOnly 不应写网页 provider conversation binding`);
|
||||
|
||||
await page.reload({ waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
@@ -259,10 +230,13 @@ async function runProviderSmoke(page, provider, suffix) {
|
||||
`${provider.key} remoteDelete reason 应说明 API Chat 无远端会话`,
|
||||
);
|
||||
|
||||
const remainingRows = sqliteJson(
|
||||
`SELECT session_id, status FROM ai_runtime_runs WHERE user_id=${sqlQuote("mnote-e2e")} AND session_id=${sqlQuote(sessionId)} AND deleted_at IS NULL LIMIT 5;`,
|
||||
[],
|
||||
);
|
||||
const remainingRows = await listAiRuntimeRuns(page.context().request, BASE_URL, {
|
||||
userId: actorId,
|
||||
workspaceId,
|
||||
sessionId,
|
||||
limit: 5,
|
||||
timeoutMs: UI_TIMEOUT_MS,
|
||||
});
|
||||
assert.strictEqual(remainingRows.length, 0, `${provider.key} 删除后 SQLite active run 不应残留`);
|
||||
|
||||
return {
|
||||
@@ -294,13 +268,6 @@ async function main() {
|
||||
|
||||
writeWorkspaceManifest(root, actorId, workspaceId);
|
||||
fs.writeFileSync(path.join(root, relativePath), ["# API ChatOnly", "", `MNOTE_API_CHAT_WORKSPACE_${suffix}`, ""].join("\n"), "utf8");
|
||||
grantWorkspaceAccess({
|
||||
actorId,
|
||||
workspaceId,
|
||||
root,
|
||||
rootUri,
|
||||
grantId: `grant_task527_api_chat_${suffix}`,
|
||||
});
|
||||
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
@@ -320,6 +287,15 @@ async function main() {
|
||||
|
||||
try {
|
||||
await ensureAuthenticated(page, context.request);
|
||||
await setupWorkspaceAccess(context.request, BASE_URL, {
|
||||
actorId,
|
||||
workspaceId,
|
||||
workspaceName: actorId,
|
||||
rootPath: root,
|
||||
rootUri,
|
||||
capabilities: ["ai", "markdown_edit"],
|
||||
timeoutMs: UI_TIMEOUT_MS,
|
||||
});
|
||||
const response = await page.goto(documentUrl(root, relativePath), {
|
||||
waitUntil: "domcontentloaded",
|
||||
timeout: UI_TIMEOUT_MS,
|
||||
@@ -333,7 +309,7 @@ async function main() {
|
||||
await saveScreenshot(page, "initial-drawer");
|
||||
|
||||
for (const provider of PROVIDERS) {
|
||||
results.push(await runProviderSmoke(page, provider, suffix));
|
||||
results.push(await runProviderSmoke(page, provider, suffix, { actorId, workspaceId }));
|
||||
}
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
|
||||
Reference in New Issue
Block a user