Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
684 lines
20 KiB
JavaScript
684 lines
20 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
bootstrapStressPageNames,
|
|
blockByUuidQuery,
|
|
blockExistsFromQueryResult,
|
|
classifyHttpResult,
|
|
classifyCliResult,
|
|
clientRuntimeOptions,
|
|
checksumDiagnosticsMatch,
|
|
cliCommand,
|
|
graphValidateArgs,
|
|
isIdleSyncStatus,
|
|
isSettledSyncStatus,
|
|
mutableStressPageNames,
|
|
offlineTodayJournalClientCount,
|
|
operationNames,
|
|
operationWeights,
|
|
parseArgs,
|
|
stressPageNames,
|
|
stressConfigText,
|
|
stressReferenceViewOrder,
|
|
syncDownloadArgs,
|
|
syncEnsureKeysArgs,
|
|
syncNeedsEnsureKeys,
|
|
syncServerStartArgs,
|
|
syncStatusUninitialized,
|
|
syncUploadArgs,
|
|
staleIdsFromContext,
|
|
todayJournalPageName,
|
|
uniqueOperationPageTitle,
|
|
} from "./cli-concurrent-edit-stress.mjs";
|
|
|
|
test("classifies stale block conflicts as race outcomes", () => {
|
|
const parsed = {
|
|
status: "error",
|
|
error: {
|
|
code: "source-not-found",
|
|
message: "source block uuid not found",
|
|
},
|
|
};
|
|
|
|
assert.deepEqual(classifyCliResult({ code: 1 }, parsed, { id: 1911 }), {
|
|
ok: false,
|
|
level: "info",
|
|
outcome: "race-conflict",
|
|
expectedRace: true,
|
|
});
|
|
});
|
|
|
|
test("classifies stale task and recycled page conflicts as race outcomes", () => {
|
|
for (const code of ["upsert-id-not-found", "recycled-page", "page-not-found"]) {
|
|
assert.equal(
|
|
classifyCliResult(
|
|
{ code: 1 },
|
|
{
|
|
status: "error",
|
|
error: { code, message: "race" },
|
|
},
|
|
{ id: 42 },
|
|
).outcome,
|
|
"race-conflict",
|
|
);
|
|
}
|
|
});
|
|
|
|
test("classifies concurrent volatile property delete as a race outcome", () => {
|
|
for (const code of ["property-not-found", "ambiguous-property-name"]) {
|
|
assert.equal(
|
|
classifyCliResult(
|
|
{ code: 1 },
|
|
{
|
|
status: "error",
|
|
error: { code, message: "volatile property race" },
|
|
},
|
|
{ op: "property-delete-recreate", phase: "delete" },
|
|
).outcome,
|
|
"race-conflict",
|
|
);
|
|
}
|
|
});
|
|
|
|
test("classifies concurrent volatile tag delete as a race outcome", () => {
|
|
assert.equal(
|
|
classifyCliResult(
|
|
{ code: 1 },
|
|
{
|
|
status: "error",
|
|
error: { code: "tag-not-found", message: "tag not found" },
|
|
},
|
|
{ op: "tag-delete-recreate", phase: "delete" },
|
|
).outcome,
|
|
"race-conflict",
|
|
);
|
|
});
|
|
|
|
test("classifies stale raw outliner block property failures as race outcomes", () => {
|
|
assert.equal(
|
|
classifyHttpResult(
|
|
{ ok: false, status: 500 },
|
|
{
|
|
error: {
|
|
code: "exception",
|
|
message: "Set block property failed: block or property doesn't exist",
|
|
},
|
|
},
|
|
{ id: 525, op: "http-set-block-property" },
|
|
).outcome,
|
|
"race-conflict",
|
|
);
|
|
});
|
|
|
|
test("classifies stale raw view targets as race outcomes", () => {
|
|
for (const message of [
|
|
'Nothing found for entity id (:block/uuid #uuid "b5cf1ff2-1403-4eed-9449-d326b1e9efad")',
|
|
'Nothing found for entity id [:block/uuid #uuid "b5cf1ff2-1403-4eed-9449-d326b1e9efad"]',
|
|
]) {
|
|
assert.equal(
|
|
classifyHttpResult(
|
|
{ ok: false, status: 500 },
|
|
{
|
|
error: {
|
|
code: "exception",
|
|
message,
|
|
},
|
|
},
|
|
{ id: 231, op: "http-create-unlinked-references-view" },
|
|
).outcome,
|
|
"race-conflict",
|
|
);
|
|
}
|
|
});
|
|
|
|
test("builds block uuid lookup queries for raw view target checks", () => {
|
|
assert.equal(
|
|
blockByUuidQuery("b5cf1ff2-1403-4eed-9449-d326b1e9efad"),
|
|
'[:find ?b . :where [?b :block/uuid #uuid "b5cf1ff2-1403-4eed-9449-d326b1e9efad"]]',
|
|
);
|
|
});
|
|
|
|
test("detects existing blocks from uuid lookup query results", () => {
|
|
assert.equal(blockExistsFromQueryResult({ parsed: { data: { result: 231 } } }), true);
|
|
assert.equal(blockExistsFromQueryResult({ parsed: { data: { result: null } } }), false);
|
|
});
|
|
|
|
test("classifies required outliner no-op results as failures", () => {
|
|
assert.deepEqual(
|
|
classifyHttpResult(
|
|
{ ok: true, status: 200 },
|
|
{ result: null },
|
|
{ op: "http-recycle-delete-page", phase: "permanent-delete", requireTruthyResult: true },
|
|
),
|
|
{
|
|
ok: false,
|
|
level: "error",
|
|
outcome: "no-op",
|
|
expectedRace: false,
|
|
},
|
|
);
|
|
});
|
|
|
|
test("keeps created-id resolution failures visible as errors", () => {
|
|
const parsed = {
|
|
status: "error",
|
|
error: {
|
|
code: "add-id-resolution-failed",
|
|
message: "unable to resolve created ids",
|
|
},
|
|
};
|
|
|
|
assert.deepEqual(classifyCliResult({ code: 1 }, parsed, { parentId: 710 }), {
|
|
ok: false,
|
|
level: "error",
|
|
outcome: "failed",
|
|
expectedRace: false,
|
|
});
|
|
});
|
|
|
|
test("extracts stale ids from operation context", () => {
|
|
assert.deepEqual(staleIdsFromContext({ id: 1, targetId: 2, parentId: 3, ids: [4, "x", 5] }), [1, 2, 3, 4, 5]);
|
|
});
|
|
|
|
test("builds local db-sync server start command from sync base", () => {
|
|
const args = syncServerStartArgs({
|
|
syncBase: "http://127.0.0.1:19090",
|
|
authPath: "/tmp/auth.json",
|
|
syncServerPidFile: "/tmp/stress/server.pid",
|
|
syncServerLogFile: "/tmp/stress/server.log",
|
|
syncServerDataDir: "/tmp/stress/server-data",
|
|
});
|
|
|
|
assert.deepEqual(args, [
|
|
"cli-e2e/scripts/db_sync_server.py",
|
|
"start",
|
|
"--repo-root",
|
|
process.cwd(),
|
|
"--pid-file",
|
|
"/tmp/stress/server.pid",
|
|
"--log-file",
|
|
"/tmp/stress/server.log",
|
|
"--data-dir",
|
|
"/tmp/stress/server-data",
|
|
"--host",
|
|
"127.0.0.1",
|
|
"--port",
|
|
"19090",
|
|
"--startup-timeout-s",
|
|
"25",
|
|
"--auth-path",
|
|
"/tmp/auth.json",
|
|
]);
|
|
});
|
|
|
|
test("parses isolated local db-sync server paths", () => {
|
|
const opts = parseArgs([
|
|
"--sync-server-pid-file",
|
|
"tmp/stress/server.pid",
|
|
"--sync-server-log-file",
|
|
"tmp/stress/server.log",
|
|
"--sync-server-data-dir",
|
|
"tmp/stress/server-data",
|
|
]);
|
|
|
|
assert.equal(opts.syncServerPidFile.endsWith("/tmp/stress/server.pid"), true);
|
|
assert.equal(opts.syncServerLogFile.endsWith("/tmp/stress/server.log"), true);
|
|
assert.equal(opts.syncServerDataDir.endsWith("/tmp/stress/server-data"), true);
|
|
});
|
|
|
|
test("stress config includes runtime auth to avoid refresh during sync start", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "logseq-stress-auth-"));
|
|
const authPath = join(dir, "auth.json");
|
|
writeFileSync(
|
|
authPath,
|
|
JSON.stringify({
|
|
"refresh-token": "refresh-1",
|
|
"id-token": "id-1",
|
|
"access-token": "access-1",
|
|
}),
|
|
);
|
|
|
|
const text = stressConfigText({
|
|
httpBase: "http://127.0.0.1:18080",
|
|
wsUrl: "ws://127.0.0.1:18080/sync/%s",
|
|
authPath,
|
|
});
|
|
|
|
assert.match(text, /:auth-path "/);
|
|
assert.match(text, /:refresh-token "refresh-1"/);
|
|
assert.match(text, /:id-token "id-1"/);
|
|
assert.match(text, /:access-token "access-1"/);
|
|
});
|
|
|
|
test("builds sync upload initialization command with e2ee password", () => {
|
|
assert.equal(syncNeedsEnsureKeys({ graphE2ee: true }), true);
|
|
|
|
assert.deepEqual(syncEnsureKeysArgs({ e2eePassword: "11111" }), [
|
|
"sync",
|
|
"ensure-keys",
|
|
"--upload-keys",
|
|
"--e2ee-password",
|
|
"11111",
|
|
]);
|
|
|
|
assert.deepEqual(syncUploadArgs({ graph: "pi-memory", e2eePassword: "11111" }), [
|
|
"sync",
|
|
"upload",
|
|
"--graph",
|
|
"pi-memory",
|
|
"--e2ee-password",
|
|
"11111",
|
|
]);
|
|
|
|
assert.equal(syncNeedsEnsureKeys({ graphE2ee: false }), false);
|
|
assert.deepEqual(syncUploadArgs({ graph: "pi-memory", e2eePassword: "11111", graphE2ee: false }), [
|
|
"sync",
|
|
"upload",
|
|
"--graph",
|
|
"pi-memory",
|
|
]);
|
|
});
|
|
|
|
test("builds final graph validation command", () => {
|
|
assert.deepEqual(graphValidateArgs({ graph: "pi-memory" }), ["graph", "validate", "--graph", "pi-memory"]);
|
|
});
|
|
|
|
test("builds isolated runtime options for additional sync clients", () => {
|
|
const seed = clientRuntimeOptions(
|
|
{
|
|
graph: "pi-memory",
|
|
config: "/tmp/stress/cli.edn",
|
|
logFile: "/tmp/stress/events.jsonl",
|
|
rootDir: "/tmp/stress/root",
|
|
homeDir: "/tmp/stress/home",
|
|
timeoutMs: 60000,
|
|
},
|
|
0,
|
|
);
|
|
const replica = clientRuntimeOptions(seed, 1);
|
|
|
|
assert.equal(seed.graph, "pi-memory");
|
|
assert.equal(replica.graph, "pi-memory");
|
|
assert.equal(seed.config, "/tmp/stress/cli.edn");
|
|
assert.equal(replica.config, "/tmp/stress/clients/client-2/cli.edn");
|
|
assert.equal(seed.env.LOGSEQ_CLI_ROOT_DIR, "/tmp/stress/root");
|
|
assert.equal(replica.env.LOGSEQ_CLI_ROOT_DIR, "/tmp/stress/clients/client-2/root");
|
|
assert.equal(replica.env.HOME, "/tmp/stress/clients/client-2/home");
|
|
});
|
|
|
|
test("parses isolated root dir for the seed client", () => {
|
|
const opts = parseArgs(["--root-dir", "/tmp/stress/root"]);
|
|
|
|
assert.equal(opts.rootDir, "/tmp/stress/root");
|
|
});
|
|
|
|
test("runs staged JavaScript CLI bundles through node", () => {
|
|
assert.deepEqual(cliCommand("/tmp/logseq-cli.js"), {
|
|
command: process.execPath,
|
|
args: ["/tmp/logseq-cli.js"],
|
|
});
|
|
assert.deepEqual(cliCommand("/usr/local/bin/logseq"), {
|
|
command: "/usr/local/bin/logseq",
|
|
args: [],
|
|
});
|
|
});
|
|
|
|
test("builds sync download command for replica clients", () => {
|
|
assert.deepEqual(syncDownloadArgs({ graph: "pi-memory", graphE2ee: false }), [
|
|
"sync",
|
|
"download",
|
|
"--graph",
|
|
"pi-memory",
|
|
]);
|
|
assert.deepEqual(syncDownloadArgs({ graph: "pi-memory", graphE2ee: true, e2eePassword: "11111" }), [
|
|
"sync",
|
|
"download",
|
|
"--graph",
|
|
"pi-memory",
|
|
"--e2ee-password",
|
|
"11111",
|
|
]);
|
|
});
|
|
|
|
test("detects final sync convergence from status checks", () => {
|
|
assert.equal(
|
|
isSettledSyncStatus({
|
|
ok: true,
|
|
parsed: {
|
|
data: {
|
|
"pending-local": 0,
|
|
"pending-server": 0,
|
|
"local-checksum": "abc",
|
|
"remote-checksum": "abc",
|
|
},
|
|
},
|
|
}),
|
|
true,
|
|
);
|
|
|
|
assert.equal(
|
|
isSettledSyncStatus({
|
|
ok: true,
|
|
parsed: {
|
|
data: {
|
|
"pending-local": 1,
|
|
"pending-server": 0,
|
|
"local-checksum": "abc",
|
|
"remote-checksum": "abc",
|
|
},
|
|
},
|
|
}),
|
|
false,
|
|
);
|
|
|
|
assert.equal(
|
|
isSettledSyncStatus({
|
|
ok: true,
|
|
parsed: {
|
|
data: {
|
|
"pending-local": 0,
|
|
"pending-server": 0,
|
|
"local-checksum": "abc",
|
|
"remote-checksum": "def",
|
|
},
|
|
},
|
|
}),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("detects idle sync status separately from cached checksums", () => {
|
|
assert.equal(
|
|
isIdleSyncStatus({
|
|
ok: true,
|
|
parsed: {
|
|
data: {
|
|
"last-error": null,
|
|
"pending-local": 0,
|
|
"pending-server": 0,
|
|
"local-tx": 40,
|
|
"remote-tx": 40,
|
|
"local-checksum": "old",
|
|
"remote-checksum": "new",
|
|
},
|
|
},
|
|
}),
|
|
true,
|
|
);
|
|
|
|
assert.equal(
|
|
isIdleSyncStatus({
|
|
ok: true,
|
|
parsed: {
|
|
data: {
|
|
"last-error": null,
|
|
"pending-local": 0,
|
|
"pending-server": 1,
|
|
"local-tx": 40,
|
|
"remote-tx": 40,
|
|
},
|
|
},
|
|
}),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("matches recomputed checksum diagnostics against remote checksum", () => {
|
|
assert.equal(
|
|
checksumDiagnosticsMatch(
|
|
new Map([
|
|
["recomputed-checksum", "abc"],
|
|
["remote-checksum", "abc"],
|
|
]),
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
checksumDiagnosticsMatch(
|
|
new Map([
|
|
["recomputed-checksum", "abc"],
|
|
["remote-checksum", "def"],
|
|
]),
|
|
),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("does not re-upload when linked graph has no remote tx before websocket start", () => {
|
|
assert.equal(
|
|
syncStatusUninitialized({
|
|
ok: true,
|
|
parsed: {
|
|
data: {
|
|
"graph-id": "ea64137c-e829-49fb-98de-dc83b745377b",
|
|
"local-tx": 55,
|
|
"remote-tx": null,
|
|
},
|
|
},
|
|
}),
|
|
false,
|
|
);
|
|
|
|
assert.equal(
|
|
syncStatusUninitialized({
|
|
ok: true,
|
|
parsed: {
|
|
data: {
|
|
"graph-id": null,
|
|
"local-tx": 55,
|
|
"remote-tx": null,
|
|
},
|
|
},
|
|
}),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("builds a multi-page stress surface with journal pages", () => {
|
|
assert.deepEqual(
|
|
stressPageNames({
|
|
page: "CLI Concurrent Stress",
|
|
extraPages: 2,
|
|
journalPages: 2,
|
|
journalStart: "2026-07-01",
|
|
}),
|
|
[
|
|
"CLI Concurrent Stress",
|
|
"CLI Concurrent Stress page 2",
|
|
"CLI Concurrent Stress page 3",
|
|
"Jul 1st, 2026",
|
|
"Jul 2nd, 2026",
|
|
],
|
|
);
|
|
});
|
|
|
|
test("mutable stress pages exclude journal pages", () => {
|
|
assert.deepEqual(
|
|
mutableStressPageNames({
|
|
page: "CLI Concurrent Stress",
|
|
extraPages: 2,
|
|
journalPages: 2,
|
|
journalStart: "2026-07-01",
|
|
}),
|
|
["CLI Concurrent Stress page 2", "CLI Concurrent Stress page 3"],
|
|
);
|
|
});
|
|
|
|
test("bootstrap stress pages exclude auto-created journal pages", () => {
|
|
assert.deepEqual(
|
|
bootstrapStressPageNames({
|
|
page: "CLI Concurrent Stress",
|
|
extraPages: 2,
|
|
journalPages: 2,
|
|
journalStart: "2026-07-01",
|
|
}),
|
|
["CLI Concurrent Stress", "CLI Concurrent Stress page 2", "CLI Concurrent Stress page 3"],
|
|
);
|
|
});
|
|
|
|
test("today journal page name uses the local calendar date", () => {
|
|
assert.equal(todayJournalPageName({}, new Date("2026-07-01T16:30:00+08:00")), "Jul 1st, 2026");
|
|
assert.equal(todayJournalPageName({ todayDate: "2026-07-02" }), "Jul 2nd, 2026");
|
|
});
|
|
|
|
test("reference view stress orders stay in generated fractional-index form", () => {
|
|
assert.equal(stressReferenceViewOrder({ seq: 0 }, 0), "a0");
|
|
assert.equal(stressReferenceViewOrder({ seq: 30 }, 0), "ay");
|
|
assert.equal(stressReferenceViewOrder({ seq: 31 }, 0), "b00");
|
|
assert.equal(stressReferenceViewOrder({ seq: 61 }, 1), "b0z");
|
|
assert.equal(stressReferenceViewOrder({ seq: 62 }, 0), "b10");
|
|
|
|
for (let seq = 0; seq < 1000; seq += 1) {
|
|
assert.match(stressReferenceViewOrder({ seq }, 0), /^(a[0-9A-Za-z]|b[0-9A-Za-z][0-9A-Za-z])$/);
|
|
assert.match(stressReferenceViewOrder({ seq }, 1), /^(a[0-9A-Za-z]|b[0-9A-Za-z][0-9A-Za-z])$/);
|
|
assert.notEqual(stressReferenceViewOrder({ seq }, 0), stressReferenceViewOrder({ seq }, 1));
|
|
}
|
|
});
|
|
|
|
test("raw page delete/restore uses unique operation-owned page titles", () => {
|
|
const state = { runId: "stress-1" };
|
|
const first = uniqueOperationPageTitle("CLI HTTP Delete Restore", state, { workerId: 3, seq: 305 });
|
|
const second = uniqueOperationPageTitle("CLI HTTP Delete Restore", state, { workerId: 3, seq: 305 });
|
|
const third = uniqueOperationPageTitle("CLI HTTP Delete Restore", state, { workerId: 4, seq: 305 });
|
|
|
|
assert.match(first, /^CLI HTTP Delete Restore stress-1 worker-3 seq-305 /);
|
|
assert.notEqual(first, second);
|
|
assert.notEqual(first, third);
|
|
});
|
|
|
|
test("operation set covers broad outliner and metadata mutations", () => {
|
|
const names = operationNames({ sync: true, offline: true });
|
|
|
|
for (const name of [
|
|
"create-root",
|
|
"create-child",
|
|
"create-tree",
|
|
"update",
|
|
"move-child",
|
|
"move-page",
|
|
"move-sibling",
|
|
"block-tags-add",
|
|
"block-tags-remove",
|
|
"block-properties-add",
|
|
"block-properties-remove",
|
|
"task-create",
|
|
"task-update",
|
|
"page-upsert",
|
|
"page-properties-add",
|
|
"page-properties-remove",
|
|
"page-delete-restore",
|
|
"tag-upsert",
|
|
"tag-delete-recreate",
|
|
"property-upsert",
|
|
"property-delete-recreate",
|
|
"http-insert-blocks",
|
|
"http-delete-blocks",
|
|
"http-move-blocks",
|
|
"http-save-block",
|
|
"http-move-up-down",
|
|
"http-indent-outdent",
|
|
"http-apply-template",
|
|
"http-create-page",
|
|
"http-rename-page",
|
|
"http-delete-restore-page",
|
|
"http-recycle-delete-page",
|
|
"http-upsert-property",
|
|
"http-set-block-property",
|
|
"http-remove-block-property",
|
|
"http-batch-set-property",
|
|
"http-batch-remove-property",
|
|
"http-batch-delete-property-value",
|
|
"http-create-property-history",
|
|
"http-create-linked-references-view",
|
|
"http-create-unlinked-references-view",
|
|
"http-toggle-reaction",
|
|
"http-delete-view-target-with-related-entities",
|
|
"http-insert-undo-redo",
|
|
"delete-single",
|
|
"delete-batch",
|
|
"delete-update-race",
|
|
"offline-window",
|
|
"offline-today-journal-race",
|
|
]) {
|
|
assert.equal(names.includes(name), true, `${name} should be in the operation set`);
|
|
}
|
|
});
|
|
|
|
test("offline operation is available only when sync offline simulation is enabled", () => {
|
|
assert.equal(operationNames({ sync: true, offline: true }).includes("offline-window"), true);
|
|
assert.equal(operationNames({ sync: true, offline: false }).includes("offline-window"), false);
|
|
assert.equal(operationNames({ sync: false, offline: true }).includes("offline-window"), false);
|
|
assert.equal(operationNames({ sync: true, offline: true }).includes("offline-today-journal-race"), true);
|
|
assert.equal(operationNames({ sync: true, offline: false }).includes("offline-today-journal-race"), false);
|
|
assert.equal(operationNames({ sync: false, offline: true }).includes("offline-today-journal-race"), false);
|
|
assert.ok(
|
|
new Map(operationWeights({ sync: true, offline: true })).get("offline-today-journal-race") >= 8,
|
|
"offline today journal race should run often enough to catch page creation conflicts",
|
|
);
|
|
});
|
|
|
|
test("offline today journal race uses actual sync clients instead of worker concurrency", () => {
|
|
assert.equal(offlineTodayJournalClientCount({ clients: 3, concurrency: 8 }), 3);
|
|
assert.equal(offlineTodayJournalClientCount({ clients: 1, concurrency: 8 }), 2);
|
|
});
|
|
|
|
test("multi-client offline operations require sync, offline mode, and more than one client", () => {
|
|
assert.equal(operationNames({ sync: true, offline: true, clients: 3 }).includes("multi-client-offline-rebase"), true);
|
|
assert.equal(operationNames({ sync: true, offline: true, clients: 3 }).includes("multi-client-today-journal-race"), true);
|
|
assert.equal(operationNames({ sync: true, offline: true, clients: 1 }).includes("multi-client-offline-rebase"), false);
|
|
assert.equal(operationNames({ sync: true, offline: false, clients: 3 }).includes("multi-client-offline-rebase"), false);
|
|
assert.equal(operationNames({ sync: false, offline: true, clients: 3 }).includes("multi-client-offline-rebase"), false);
|
|
});
|
|
|
|
test("3k sync stress includes view-related history references and reactions", () => {
|
|
const maxOps = 3000;
|
|
const weights = new Map(operationWeights({ sync: true, offline: true, clients: 3 }));
|
|
const totalWeight = [...weights.values()].reduce((sum, weight) => sum + weight, 0);
|
|
|
|
for (const name of [
|
|
"http-create-property-history",
|
|
"http-create-linked-references-view",
|
|
"http-create-unlinked-references-view",
|
|
"http-toggle-reaction",
|
|
"http-delete-view-target-with-related-entities",
|
|
]) {
|
|
assert.ok(weights.has(name), `${name} should be in the sync stress operation set`);
|
|
assert.ok((weights.get(name) / totalWeight) * maxOps >= 10, `${name} should run often enough in a 3k-op stress run`);
|
|
}
|
|
});
|
|
|
|
test("critical outliner mutations dominate the stress distribution", () => {
|
|
const weights = new Map(operationWeights({ sync: true, offline: true }));
|
|
const criticalOps = [
|
|
"create-root",
|
|
"create-child",
|
|
"create-tree",
|
|
"move-child",
|
|
"move-page",
|
|
"move-sibling",
|
|
"http-insert-blocks",
|
|
"http-delete-blocks",
|
|
"http-move-blocks",
|
|
"http-move-up-down",
|
|
"http-indent-outdent",
|
|
"http-delete-restore-page",
|
|
"http-delete-view-target-with-related-entities",
|
|
"http-insert-undo-redo",
|
|
"delete-single",
|
|
"delete-batch",
|
|
"delete-update-race",
|
|
];
|
|
const totalWeight = [...weights.values()].reduce((sum, weight) => sum + weight, 0);
|
|
const criticalWeight = criticalOps.reduce((sum, name) => sum + (weights.get(name) || 0), 0);
|
|
|
|
assert.ok(criticalWeight / totalWeight >= 0.65, "insert/delete/move/undo-redo operations should dominate the run");
|
|
assert.ok((weights.get("http-insert-undo-redo") || 0) >= 8, "undo/redo should run often enough to catch regressions");
|
|
assert.ok((weights.get("http-insert-blocks") || 0) >= 8, "raw insert should run often enough to catch regressions");
|
|
assert.ok((weights.get("http-delete-blocks") || 0) >= 8, "raw delete should run often enough to catch regressions");
|
|
assert.ok((weights.get("http-move-blocks") || 0) >= 8, "raw move should run often enough to catch regressions");
|
|
});
|