Verify retained feature block after restart

This commit is contained in:
vorotamoroz
2026-09-27 10:15:46 +00:00
parent 6c50505096
commit 9a047d44f3
3 changed files with 80 additions and 8 deletions
+1 -1
View File
@@ -200,7 +200,7 @@ This proves in real Obsidian the plug-in behaviour shared by supported platforms
`test:e2e:obsidian:customisation-sync` runs a two-vault Customisation Sync workflow. It scans a real snippet CSS file, config JSON file, and sample plug-in fixture into per-file Customisation Sync data, synchronises the entries through CouchDB, applies them on the second vault, verifies the resulting `.obsidian` files, propagates a snippet update, and verifies deletion of the source-vault snippet sync data without confusing it with the target vault's own applied copy.
`test:e2e:obsidian:remote-feature-change` starts real Obsidian with continuous CouchDB replication, then changes the remote version document from generation 12 to generation 13 with an unknown feature. It waits for the control document to reach the local database and the active Replicator to retire, checks that another replication is refused, and verifies that an already accepted Vault note remains intact. It is a focused test outside `test:e2e:obsidian:local-suite`; pending-work recovery with a future compatible client remains a separate validation boundary.
`test:e2e:obsidian:remote-feature-change` starts real Obsidian with continuous CouchDB replication, then changes the remote version document from generation 12 to generation 13 with an unknown feature. It waits for the control document to reach the local database and the active Replicator to retire, checks that another replication is refused, and verifies that an already accepted Vault note remains intact. It then shortens the feature lists in the remote and local control documents, restarts the same Vault, and verifies that the saved observation still blocks OneShot and Continuous replication. It is a focused test outside `test:e2e:obsidian:local-suite`; pending-work recovery with a future compatible client remains a separate validation boundary.
`test:e2e:obsidian:setting-markdown-export` enables setting Markdown export, waits for the generated Markdown file in the vault, and verifies that credentials are omitted when `writeCredentialsForSettingSync=false`.
@@ -32,6 +32,9 @@ const unknownFeature = "future-format-v7";
type FeatureState = {
version: number | null;
features: string[];
snapshotFeatures: string[];
snapshotDatabaseId: string | null;
databaseId: string | null;
hasActiveReplicator: boolean;
};
@@ -43,9 +46,14 @@ async function readFeatureState(cliBinary: string, env: NodeJS.ProcessEnv): Prom
"const core=app.plugins.plugins['obsidian-livesync'].core;",
`const id=${JSON.stringify(VERSIONING_DOCID)};`,
"const info=await core.localDatabase.getRaw(id).catch(()=>null);",
"const snapshot=await core.kvDB.get('replicationResultProcessorSnapshot');",
"const databaseId=await core.localDatabase.localDatabase.id();",
"return JSON.stringify({",
"version:typeof info?.version==='number'?info.version:null,",
"features:Array.isArray(info?.used_features)?info.used_features:[],",
"snapshotFeatures:Array.isArray(snapshot?.observedFeatures)?snapshot.observedFeatures:[],",
"snapshotDatabaseId:snapshot?.databaseId??null,",
"databaseId,",
"hasActiveReplicator:!!core.services.replicator.getActiveReplicator(),",
"});",
"})()",
@@ -145,8 +153,12 @@ async function main(): Promise<void> {
const observed = await waitForState(
cli.binary,
session.cliEnv,
(state) => state.version === 13 && state.features.includes(unknownFeature) && !state.hasActiveReplicator,
"the live feature change and Replicator retirement"
(state) =>
state.version === 13 &&
state.features.includes(unknownFeature) &&
state.snapshotFeatures.includes(unknownFeature) &&
!state.hasActiveReplicator,
"the live feature change, durable observation, and Replicator retirement"
);
const replicated = await evalObsidianJson<boolean>(
@@ -158,8 +170,63 @@ async function main(): Promise<void> {
const acceptedAfterStop = await readFile(fullPath, "utf-8");
if (acceptedAfterStop !== acceptedContent)
throw new Error("Previously accepted Vault content changed on stop.");
// Shorten both visible lists so only the snapshot can retain the observed requirement.
const shortenedRemoteVersion = await fetchCouchDbDocument(couchDb, dbName, VERSIONING_DOCID);
await putCouchDbDocument(couchDb, dbName, { ...shortenedRemoteVersion, used_features: [] });
const shortenedLocalFeatures = await evalObsidianJson<string[]>(
cli.binary,
[
"(async()=>{",
"const core=app.plugins.plugins['obsidian-livesync'].core;",
`const id=${JSON.stringify(VERSIONING_DOCID)};`,
"const info=await core.localDatabase.getRaw(id);",
"await core.localDatabase.putRaw({...info,used_features:[]});",
"return JSON.stringify((await core.localDatabase.getRaw(id)).used_features);",
"})()",
].join(""),
session.cliEnv
);
if (shortenedLocalFeatures.length !== 0)
throw new Error("The local control document did not shorten for the restart scenario.");
await session.app.stop();
session = undefined;
session = await startObsidianLiveSyncSession({ binary, cliBinary: cli.binary, vault });
await waitForLiveSyncCoreReady(cli.binary, session.cliEnv);
const afterRestart = await waitForState(
cli.binary,
session.cliEnv,
(state) =>
state.version === 13 && state.features.length === 0 && state.snapshotFeatures.includes(unknownFeature),
"the retained unknown feature after restart"
);
if (afterRestart.snapshotDatabaseId !== afterRestart.databaseId)
throw new Error("The retained feature observation belongs to a different physical database.");
const replicatedAfterRestart = await evalObsidianJson<boolean>(
cli.binary,
"(async()=>JSON.stringify(!!(await app.plugins.plugins['obsidian-livesync'].core.services.replication.replicate(true))))()",
session.cliEnv
);
const continuousAfterRestart = await evalObsidianJson<{ status: string }>(
cli.binary,
[
"(async()=>{",
"const core=app.plugins.plugins['obsidian-livesync'].core;",
"const result=await core.services.replication.startContinuous({trigger:'daemon',interaction:{kind:'forbidden'}});",
"return JSON.stringify(result);",
"})()",
].join(""),
session.cliEnv
);
if (replicatedAfterRestart || continuousAfterRestart.status !== "blocked")
throw new Error(
`Replication resumed after restart despite a previously observed unknown feature: ${JSON.stringify({ afterRestart, replicatedAfterRestart, continuousAfterRestart })}`
);
if ((await readFile(fullPath, "utf-8")) !== acceptedContent)
throw new Error("Previously accepted Vault content changed after restart.");
console.log(
`Active feature change retired the Replicator and kept accepted content: ${JSON.stringify(observed)}`
`Active feature change retired the Replicator; a shortened control document did not clear the block after restart: ${JSON.stringify({ observed, afterRestart })}`
);
} finally {
await session?.app.stop();