Merge tested Customisation Sync timestamps and current main

This commit is contained in:
vorotamoroz committed 2026-09-30 16:42:43 +00:00
commit fc71c2a9e7
129 files changed
+8133 -551

No files matched your search

@@ -52,7 +52,7 @@ vi.mock("@/common/obsidianCommunityPlugins.ts", () => ({
}));
import { cancelTask } from "@/common/utils.ts";
import { ConfigSync } from "./CmdConfigSync";
import { ConfigSync, PluginDataExDisplayV2, type IPluginDataExDisplay } from "./CmdConfigSync";
describe("ConfigSync commands", () => {
it("shows the Customisation Sync command only whilst the feature is enabled", () => {
@@ -111,3 +111,25 @@ describe("ConfigSync commands", () => {
expect(periodicPluginSweepProcessor.disable).toHaveBeenCalledOnce();
});
});
describe("PluginDataExDisplayV2", () => {
const entryWithModifiedTimes = (mtimes: number[]) =>
new PluginDataExDisplayV2({
documentPath: "ix:device/plugin_main/example",
category: "PLUGIN_MAIN",
name: "example",
term: "device",
files: mtimes.map((mtime, index) => ({ filename: `file-${index}`, mtime, data: [] })),
} as unknown as IPluginDataExDisplay);
it("averages millisecond modification times without truncating them to 32 bits", () => {
const mtime = Date.UTC(2026, 8, 15);
expect(entryWithModifiedTimes([mtime, mtime, mtime]).mtime).toBe(mtime);
});
it("keeps a newer copy newer than an older one", () => {
const newer = entryWithModifiedTimes([Date.UTC(2026, 8, 15)]);
const older = entryWithModifiedTimes([Date.UTC(2026, 8, 2)]);
expect(newer.mtime).toBeGreaterThan(older.mtime);
});
});
+2 -1
View File
@@ -368,7 +368,8 @@ export class PluginDataExDisplayV2 {
return this._version;
}
get mtime(): number {
return ~~this.files.reduce((a, b) => a + b.mtime, 0) / this.files.length;
// Millisecond timestamps exceed 32 bits, so `~~` would wrap them.
return Math.floor(this.files.reduce((a, b) => a + b.mtime, 0) / this.files.length);
}
}
export type PluginDataEx = {
@@ -0,0 +1,69 @@
<script lang="ts">
import type { P2PConnectionInfo } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { CLOUDFLARE_TURN_TYPE } from "@/integrations/cloudflare/settings";
import { validateManagedTurnSettings } from "@/integrations/turnSettings";
import { translateLiveSyncMessage as translate, translateIfAvailable } from "@/common/translation";
type TurnSettings = Pick<P2PConnectionInfo, "P2P_turnServers" | "P2P_turnUsername" | "P2P_turnCredential" | "P2P_managedType" | "P2P_managedId" | "P2P_managedToken">;
let { settings = $bindable() }: { settings: TurnSettings } = $props();
const managedType = $derived(settings.P2P_managedType ?? "");
const error = $derived(validateManagedTurnSettings(settings));
function selectProvider(type: string) {
settings.P2P_managedType = type || undefined;
settings.P2P_managedId = type ? "" : undefined;
settings.P2P_managedToken = type ? "" : undefined;
}
</script>
<div class="turn-configuration">
<label>
<span>{translate("TURN configuration")}</span>
<select aria-label={translate("TURN configuration")} name="p2p-turn-source" value={managedType} onchange={(event) => selectProvider(event.currentTarget.value)}>
<option value="">{translate("Manual")}</option>
<option value={CLOUDFLARE_TURN_TYPE}>{translate("Managed (Cloudflare)")}</option>
{#if managedType !== "" && managedType !== CLOUDFLARE_TURN_TYPE}
<option value={managedType} disabled>{translate("Unsupported TURN configuration")}</option>
{/if}
</select>
</label>
{#if managedType === ""}
<label>
<span>{translate("TURN Server URLs (comma-separated)")}</span>
<textarea name="p2p-turn-servers" rows="3" placeholder="turn:turn.example.com:3478"
bind:value={settings.P2P_turnServers} autocapitalize="off" spellcheck="false"></textarea>
</label>
<label>
<span>{translate("TURN Username")}</span>
<input type="text" name="p2p-turn-username" placeholder={translate("Enter TURN username")} bind:value={settings.P2P_turnUsername}
autocomplete="off" autocapitalize="off" spellcheck="false" />
</label>
<label>
<span>{translate("TURN Credential")}</span>
<input type="password" name="p2p-turn-credential" placeholder={translate("Enter TURN credential")} bind:value={settings.P2P_turnCredential}
autocomplete="new-password" />
</label>
{:else if managedType === CLOUDFLARE_TURN_TYPE}
<label>
<span>{translate("TURN Key ID")}</span>
<input type="text" name="p2p-turn-turnKeyId" bind:value={settings.P2P_managedId}
autocomplete="off" autocapitalize="off" spellcheck="false" />
</label>
<label>
<span>{translate("TURN Key API Token")}</span>
<input type="password" name="p2p-turn-apiToken" bind:value={settings.P2P_managedToken}
autocomplete="new-password" autocapitalize="off" spellcheck="false" />
</label>
<p>{translate("The API token is saved with this profile and included in Setup URI and QR code sharing. Temporary TURN credentials are kept in memory only.")}</p>
{/if}
{#if error}
<p role="status" class="turn-error">{translateIfAvailable(error)}</p>
{/if}
</div>
<style>
label { display: grid; gap: 0.25rem; margin: 0.75rem 0; }
input, textarea, select { box-sizing: border-box; width: 100%; }
p { font-size: var(--font-ui-small, 0.9rem); }
.turn-error { color: var(--text-error, #b33); }
</style>
@@ -1,9 +1,6 @@
import type { ObsidianLiveSyncSettings, SettingsMigrationState } from "@vrtmrz/livesync-commonlib/settings";
import type { CompatibilityPause } from "@/common/databaseCompatibility.ts";
import type {
ReviewHarnessScenarioResult,
ReviewHarnessScenarioStatus,
} from "./reviewHarnessTypes";
import type { ReviewHarnessScenarioResult, ReviewHarnessScenarioStatus } from "./reviewHarnessTypes";
export type { ReviewHarnessScenarioResult, ReviewHarnessScenarioStatus } from "./reviewHarnessTypes";
@@ -32,6 +29,14 @@ export const REVIEW_HARNESS_SCENARIOS = [
mode: "automatic",
access: "dedicated-vault-fixtures",
},
{
id: "id-generation-performance",
title: "ID generation performance",
description:
"Measures legacy and independent IDs with fixed in-memory inputs. Reports time per 1,000 IDs and per ID, key derivation time, and JavaScript heap samples where available. Keep Obsidian in the foreground.",
mode: "automatic",
access: "read-only",
},
] as const;
export const REVIEW_HARNESS_SCENARIO_IDS = REVIEW_HARNESS_SCENARIOS.map(({ id }) => id);
@@ -114,7 +119,9 @@ const NEW_VAULT_RECOMMENDATION_KEYS = [
"E2EEAlgorithm",
] as const;
type LifecycleSettingKey = (typeof PRESERVED_SYNC_SETTING_KEYS)[number] | (typeof NEW_VAULT_RECOMMENDATION_KEYS)[number];
type LifecycleSettingKey =
| (typeof PRESERVED_SYNC_SETTING_KEYS)[number]
| (typeof NEW_VAULT_RECOMMENDATION_KEYS)[number];
type SettingsForLifecycleInspection = Partial<Pick<ObsidianLiveSyncSettings, LifecycleSettingKey>>;
export function inspectSettingsLifecycle(input: {
@@ -130,9 +137,7 @@ export function inspectSettingsLifecycle(input: {
};
}
const invalidSyncSettings = PRESERVED_SYNC_SETTING_KEYS.filter(
(key) => typeof input.settings[key] !== "boolean"
);
const invalidSyncSettings = PRESERVED_SYNC_SETTING_KEYS.filter((key) => typeof input.settings[key] !== "boolean");
if (invalidSyncSettings.length > 0) {
return {
status: "failed",
@@ -205,6 +210,7 @@ export interface ReviewHarnessReportScenario {
readonly mode: ReviewHarnessScenarioMode;
readonly status: ReviewHarnessScenarioStatus;
readonly detail: string;
readonly observations?: readonly string[];
}
export interface ReviewHarnessReportInput {
@@ -248,13 +254,15 @@ export function formatReviewHarnessReport(input: ReviewHarnessReportInput): stri
);
const scenarios = table(
["Scenario", "Mode", "Status", "Detail"],
input.scenarios.map(({ id, title, mode, status, detail }) => [
`${title} (${id})`,
mode,
status,
detail,
])
input.scenarios.map(({ id, title, mode, status, detail }) => [`${title} (${id})`, mode, status, detail])
);
const observations = input.scenarios
.filter((scenario) => scenario.observations?.length)
.map(
({ title, observations }) =>
`### ${title}\n\n${observations!.map((value) => `- ${tableCell(value)}`).join("\n")}`
)
.join("\n\n");
return `## Self-hosted LiveSync Review Harness report
Generated at \`${tableCell(input.generatedAt)}\`.
@@ -267,6 +275,8 @@ ${environment}
${scenarios}
${observations}
<details>
<summary>Event transcript</summary>
@@ -75,6 +75,7 @@ describe("Review Harness contract", () => {
"settings-lifecycle",
"compatibility-review",
"vault-round-trip",
"id-generation-performance",
]);
});
@@ -21,6 +21,7 @@ export interface ReviewHarnessRuntime {
getCompatibilityPause(): CompatibilityPause | undefined;
openCompatibilityReview(): Promise<void>;
runVaultRoundTrip(): Promise<ReviewHarnessScenarioResult>;
runIdBenchmark(): Promise<ReviewHarnessScenarioResult>;
readContinuation(): string | null;
writeContinuation(value: string): void;
deleteContinuation(): void;
@@ -159,6 +160,8 @@ export class ReviewHarnessController {
});
} else if (id === "vault-round-trip") {
result = await this.runtime.runVaultRoundTrip();
} else if (id === "id-generation-performance") {
result = await this.runtime.runIdBenchmark();
} else {
const inspection = this.inspectCompatibilityReview();
result =
@@ -206,10 +209,7 @@ export class ReviewHarnessController {
detail: "The device-local compatibility review remains pending.",
observations: inspection.observations,
};
this.record(
"compatibility-review-updated",
this.results["compatibility-review"].status
);
this.record("compatibility-review-updated", this.results["compatibility-review"].status);
} catch (error) {
this.setUnexpectedFailure("compatibility-review", error);
} finally {
@@ -259,6 +259,7 @@ export class ReviewHarnessController {
mode,
status: this.results[id].status,
detail: this.results[id].detail,
observations: this.results[id].observations,
})),
transcript: this.transcript,
});
@@ -80,6 +80,11 @@ function createRuntime(): ReviewHarnessRuntime & {
detail: "The owned fixture tree was exercised and removed.",
observations: [],
})),
runIdBenchmark: vi.fn(async () => ({
status: "passed" as const,
detail: "ID generation measurements completed.",
observations: ["Chunk 256 B: 1000 IDs total=43.00 ms; per ID=0.0430 ms"],
})),
readContinuation() {
return this.continuation;
},
@@ -150,6 +155,60 @@ describe("ReviewHarnessController", () => {
expect(runtime.reportError).toHaveBeenCalledOnce();
});
it("runs ID measurements on request and includes their units in the copied report", async () => {
const runtime = createRuntime();
const controller = new ReviewHarnessController(runtime);
await controller.runAutomaticScenarios();
expect(runtime.runIdBenchmark).not.toHaveBeenCalled();
await controller.runScenario("id-generation-performance");
await controller.copyReport();
expect(runtime.runIdBenchmark).toHaveBeenCalledOnce();
expect(controller.snapshot().results["id-generation-performance"].status).toBe("passed");
expect(vi.mocked(runtime.copyText).mock.calls[0][0]).toContain("1000 IDs total=43.00 ms; per ID=0.0430 ms");
expect(runtime.runVaultRoundTrip).not.toHaveBeenCalled();
expect(runtime.events).toEqual([]);
expect(runtime.continuation).toBeNull();
});
it("excludes an unexpected measurement error from the copied report", async () => {
const runtime = createRuntime();
runtime.runIdBenchmark = vi.fn().mockRejectedValue(new Error("private measurement error"));
const controller = new ReviewHarnessController(runtime);
await controller.runScenario("id-generation-performance");
expect(controller.snapshot().results["id-generation-performance"].status).toBe("failed");
expect(controller.createReport()).not.toContain("private measurement error");
expect(runtime.reportError).toHaveBeenCalledOnce();
});
it("does not overlap an ID measurement with another scenario", async () => {
const runtime = createRuntime();
let finish!: () => void;
const pending = new Promise<void>((resolve) => {
finish = resolve;
});
runtime.runIdBenchmark = vi.fn(async () => {
await pending;
return { status: "passed" as const, detail: "Measured", observations: [] };
});
const controller = new ReviewHarnessController(runtime);
const running = controller.runScenario("id-generation-performance");
await controller.runScenario("id-generation-performance");
await controller.runScenario("vault-round-trip");
expect(runtime.runIdBenchmark).toHaveBeenCalledOnce();
expect(runtime.runVaultRoundTrip).not.toHaveBeenCalled();
expect(controller.snapshot().running).toBe(true);
finish();
await running;
expect(controller.snapshot().running).toBe(false);
});
it("deletes a one-shot continuation before exposing the resumed guided step", () => {
const runtime = createRuntime();
runtime.continuation = JSON.stringify({
@@ -167,9 +226,7 @@ describe("ReviewHarnessController", () => {
expect(controller.snapshot().results["compatibility-review"]).toMatchObject({
status: "waiting-for-user",
});
expect(controller.snapshot().resumedRequestId).toBe(
"compatibility-review-2026-07-18T11:59:00.000Z"
);
expect(controller.snapshot().resumedRequestId).toBe("compatibility-review-2026-07-18T11:59:00.000Z");
});
it("does not copy rejected continuation values into the report", () => {
@@ -0,0 +1,109 @@
import type { ReviewHarnessScenarioResult } from "./reviewHarnessTypes";
export interface IdBenchmarkOperations {
deriveKey(): Promise<unknown>;
chunkId(piece: string, independent: boolean): Promise<string>;
documentId(path: string, independent: boolean): Promise<string>;
}
type BenchmarkPerformance = Pick<Performance, "now"> & {
readonly memory?: { readonly usedJSHeapSize: number };
};
const ID_COUNT = 1000;
const SAMPLES = 3;
const BATCH_SIZE = 100;
const WARMUP_COUNT = 32;
function readHeap(clock: BenchmarkPerformance): number | undefined {
try {
const bytes = clock.memory?.usedJSHeapSize;
return typeof bytes === "number" && Number.isFinite(bytes) && bytes >= 0 ? bytes : undefined;
} catch {
return undefined;
}
}
function summary(samples: readonly number[]): string {
const sorted = [...samples].sort((a, b) => a - b);
return `median=${sorted[1].toFixed(2)} ms; range=${sorted[0].toFixed(2)}–${sorted[2].toFixed(2)} ms`;
}
export async function runReviewHarnessIdBenchmark(
operations: IdBenchmarkOperations,
clock: BenchmarkPerformance = performance,
yieldControl: () => Promise<void> = () => new Promise((resolve) => window.setTimeout(resolve, 0))
): Promise<ReviewHarnessScenarioResult> {
const before = readHeap(clock);
let highest = before;
const sampleHeap = () => {
const value = readHeap(clock);
if (value !== undefined) highest = Math.max(highest ?? value, value);
return value;
};
const observations = [
"Fixed synthetic inputs; 3 samples, alternating legacy/independent order; 32 warm-up IDs per sample. Legacy Chunk algorithm: xxhash64.",
"Compute timings include input construction and awaited ID generation. Initialisation, warm-up, and pauses between batches are excluded. This does not measure a Rebuild or remote transfer.",
];
const derivationSamples: number[] = [];
for (let sample = 0; sample < SAMPLES; sample++) {
await yieldControl();
const started = clock.now();
await operations.deriveKey();
derivationSamples.push(clock.now() - started);
sampleHeap();
}
observations.push(`ID key derivation at save time: ${summary(derivationSamples)} per derivation.`);
const cases = [
...[256, 4096, 32768].map((bytes) => {
const prefix = "r".repeat(bytes - 8);
return {
label: `Chunk IDs, ${bytes} B`,
run: (i: number, independent: boolean) =>
operations.chunkId(prefix + i.toString(36).padStart(8, "0"), independent),
};
}),
{
label: "Obfuscated document IDs",
run: (i: number, independent: boolean) => operations.documentId(`benchmark/path-${i}.md`, independent),
},
];
for (const scenario of cases) {
const samples: [number[], number[]] = [[], []];
for (let sample = 0; sample < SAMPLES; sample++) {
for (const independent of sample % 2 === 0 ? [false, true] : [true, false]) {
for (let i = 0; i < WARMUP_COUNT; i++) await scenario.run(i, independent);
let elapsed = 0;
for (let batch = 0; batch < ID_COUNT; batch += BATCH_SIZE) {
await yieldControl();
const started = clock.now();
for (let i = batch; i < batch + BATCH_SIZE; i++) await scenario.run(i, independent);
elapsed += clock.now() - started;
sampleHeap();
}
samples[independent ? 1 : 0].push(elapsed);
}
}
for (const [index, values] of samples.entries()) {
const median = [...values].sort((a, b) => a - b)[1];
observations.push(
`${scenario.label}, ${index === 0 ? "legacy" : "independent"}: ${ID_COUNT} IDs total ${summary(values)}; per ID=${(median / ID_COUNT).toFixed(4)} ms.`
);
}
}
const after = sampleHeap();
if (highest === undefined) {
observations.push("JavaScript heap: unavailable on this device.");
} else {
const mib = (bytes: number | undefined) =>
bytes === undefined ? "unavailable" : `${(bytes / 1048576).toFixed(2)} MiB`;
observations.push(
`JavaScript heap: before=${mib(before)}; highest sampled=${mib(highest)}; after=${mib(after)}.`
);
}
observations.push(
"Heap samples are approximate, may include other Obsidian work, and are affected by garbage collection. They are neither total app RAM nor a true peak."
);
return { status: "passed", detail: "ID generation measurements completed.", observations };
}
@@ -0,0 +1,99 @@
import { describe, expect, it } from "vitest";
import { runReviewHarnessIdBenchmark, type IdBenchmarkOperations } from "./reviewHarnessIdBenchmark";
function fixture() {
let elapsed = 0;
let derivations = 0;
const chunkCounts = [0, 0];
const documentCounts = [0, 0];
const chunkSizes = new Set<number>();
const operations: IdBenchmarkOperations = {
deriveKey: () => {
derivations++;
elapsed += 42;
return Promise.resolve("private-derived-key");
},
chunkId: (piece, independent) => {
chunkCounts[independent ? 1 : 0]++;
chunkSizes.add(piece.length);
elapsed += independent ? 2 : 1;
return Promise.resolve("private-chunk-id");
},
documentId: (_path, independent) => {
documentCounts[independent ? 1 : 0]++;
elapsed += independent ? 4 : 3;
return Promise.resolve("private-document-id");
},
};
return {
operations,
now: () => elapsed,
yieldControl: () => {
elapsed += 100;
return Promise.resolve();
},
counts: () => ({ derivations, chunkCounts, documentCounts, chunkSizes: [...chunkSizes] }),
};
}
describe("Review Harness ID measurements", () => {
it("reports totals and per-ID timings separately, excluding warm-up and cooperative pauses", async () => {
const f = fixture();
const result = await runReviewHarnessIdBenchmark(f.operations, { now: f.now }, f.yieldControl);
const report = result.observations.join("\n");
expect(result.status).toBe("passed");
expect(report).toContain("1000 IDs total median=1000.00 ms; range=1000.00–1000.00 ms; per ID=1.0000 ms");
expect(report).toContain("1000 IDs total median=2000.00 ms; range=2000.00–2000.00 ms; per ID=2.0000 ms");
expect(report).toContain("Obfuscated document IDs, legacy: 1000 IDs total median=3000.00 ms");
expect(report).toContain("Obfuscated document IDs, independent: 1000 IDs total median=4000.00 ms");
expect(report).toContain("ID key derivation at save time: median=42.00 ms");
expect(report).toContain("JavaScript heap: unavailable on this device.");
expect(report).not.toContain("private-");
expect(f.counts()).toEqual({
derivations: 3,
chunkCounts: [9288, 9288],
documentCounts: [3096, 3096],
chunkSizes: [256, 4096, 32768],
});
});
it("labels the highest sampled heap separately from total app RAM and allows a lower final sample", async () => {
const f = fixture();
let reads = 0;
const clock = {
now: f.now,
get memory() {
return { usedJSHeapSize: (reads++ === 0 ? 2 : reads === 2 ? 5 : 1) * 1048576 };
},
};
const result = await runReviewHarnessIdBenchmark(f.operations, clock, f.yieldControl);
expect(result.observations).toContain(
"JavaScript heap: before=2.00 MiB; highest sampled=5.00 MiB; after=1.00 MiB."
);
expect(result.observations.join("\n")).toContain("neither total app RAM nor a true peak");
});
it.each([Number.NaN, Number.POSITIVE_INFINITY, -1, "throws"])(
"keeps timings usable when the heap API returns %s",
async (value) => {
const f = fixture();
const result = await runReviewHarnessIdBenchmark(
f.operations,
{
now: f.now,
get memory() {
if (value === "throws") throw new Error("Heap API unavailable");
return { usedJSHeapSize: value as number };
},
},
f.yieldControl
);
expect(result.status).toBe("passed");
expect(result.observations).toContain("JavaScript heap: unavailable on this device.");
expect(result.observations.join("\n")).not.toMatch(/NaN|Infinity|private-/u);
}
);
});
@@ -0,0 +1,35 @@
import { DEFAULT_SETTINGS, deriveIdKey } from "@vrtmrz/livesync-commonlib/settings";
import { path2id_base } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
import type { FilePath } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { HashManager } from "@vrtmrz/livesync-commonlib/hashing";
import type { IdBenchmarkOperations } from "./reviewHarnessIdBenchmark";
const FIXTURE_PASSPHRASE = "Self-hosted LiveSync ID benchmark passphrase";
const FIXTURE_SOURCE = "Self-hosted LiveSync ID benchmark source";
const FIXTURE_KEY = "ab".repeat(32);
export async function createIdBenchmarkOperations(): Promise<IdBenchmarkOperations> {
const managers: HashManager[] = [];
for (const independent of [false, true]) {
const settings = Object.freeze({
...DEFAULT_SETTINGS,
encrypt: true,
passphrase: FIXTURE_PASSPHRASE,
hashAlg: "xxhash64" as const,
idDerivationVersion: independent ? (1 as const) : (0 as const),
idDerivationKey: independent ? FIXTURE_KEY : "",
});
// HashManager only reads currentSettings; this fixture has no storage or live service access.
const settingService = { currentSettings: () => settings } as HashManager["options"]["settingService"];
const manager = new HashManager({ settingService });
if (!(await manager.initialise())) throw new Error("The benchmark hash manager could not initialise.");
managers.push(manager);
}
return {
deriveKey: () => deriveIdKey(FIXTURE_SOURCE),
chunkId: (piece, independent) => managers[independent ? 1 : 0].computeHash(piece),
// Fixture paths are already normalised; use the same ID calculation as PathService.
documentId: (path, independent) =>
path2id_base(path as FilePath, FIXTURE_PASSPHRASE, false, independent ? FIXTURE_KEY : undefined),
};
}
@@ -0,0 +1,38 @@
import { describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS } from "@vrtmrz/livesync-commonlib/settings";
import { createIdBenchmarkOperations } from "./reviewHarnessIdBenchmarkRuntime";
describe("Review Harness benchmark implementation", () => {
it("uses the packaged legacy and independent algorithms with isolated fixed settings", async () => {
const originalDefaults = structuredClone(DEFAULT_SETTINGS);
const fetch = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("Network access is forbidden"));
try {
const operations = await createIdBenchmarkOperations();
const chunk = "r".repeat(256);
const legacy = await operations.chunkId(chunk, false);
const independent = await operations.chunkId(chunk, true);
expect(legacy).toMatch(/^\+[0-9a-z]{1,13}$/u);
expect(independent).toMatch(/^\+[0-9a-f]{64}$/u);
expect(independent).toBe("+9223e53d99e80c29effee9e95e38ed168d13c14f717054f9e996a1cd0a597000");
expect(await operations.chunkId(chunk, false)).toBe(legacy);
expect(await operations.chunkId(chunk, true)).toBe(independent);
expect(await operations.chunkId("s".repeat(256), true)).not.toBe(independent);
const legacyPath = await operations.documentId("benchmark/path-1.md", false);
const independentPath = await operations.documentId("benchmark/path-1.md", true);
expect(legacyPath).toMatch(/^f:[0-9a-f]{64}$/u);
expect(independentPath).toMatch(/^f:[0-9a-f]{64}$/u);
expect(legacyPath).not.toBe(independentPath);
expect(await operations.documentId("benchmark/path-1.md", true)).toBe(independentPath);
const second = await createIdBenchmarkOperations();
expect(await second.chunkId(chunk, true)).toBe(independent);
expect(await operations.deriveKey()).toMatch(/^[0-9a-f]{64}$/u);
expect(fetch).not.toHaveBeenCalled();
expect(DEFAULT_SETTINGS).toEqual(originalDefaults);
} finally {
fetch.mockRestore();
}
});
});