import test from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; test("auto-sync establishes the initial data baseline before debouncing edits", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const baselineCommentIndex = source.indexOf("Establish the initial baseline immediately"); const baselineHelperIndex = source.indexOf("const establishInitialBaseline = () =>", baselineCommentIndex); const initializedAssignmentIndex = source.indexOf("isInitializedRef.current = true;", baselineHelperIndex); const hashReadIndex = source.indexOf("const currentHash = await getDataHash();", baselineHelperIndex); const initializationGuardIndex = source.indexOf("if (!isInitializedRef.current)", baselineCommentIndex); const baselineCallIndex = source.indexOf("establishInitialBaseline();", initializationGuardIndex); const debounceCommentIndex = source.indexOf("Debounce first, then build the expensive full-data hash", initializationGuardIndex); const debounceTimerIndex = source.indexOf("syncTimeoutRef.current = setTimeout", debounceCommentIndex); assert.notEqual(baselineCommentIndex, -1); assert.notEqual(baselineHelperIndex, -1); assert.notEqual(initializationGuardIndex, -1); assert.notEqual(initializedAssignmentIndex, -1); assert.notEqual(hashReadIndex, -1); assert.notEqual(baselineCallIndex, -1); assert.notEqual(debounceCommentIndex, -1); assert.notEqual(debounceTimerIndex, -1); assert.ok( initializedAssignmentIndex < hashReadIndex, "initialization must be marked synchronously before reading the baseline hash", ); assert.ok( baselineCallIndex < debounceTimerIndex, "the first baseline hash must be captured before scheduling the debounced auto-sync timer", ); }); test("paused convergent sync captures a baseline before returning", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const effectIndex = source.indexOf("// Debounced auto-sync when data changes"); const pausedGuardIndex = source.indexOf("if (convergentSyncPaused)", effectIndex); const initializationGuardIndex = source.indexOf("if (!isInitializedRef.current)", pausedGuardIndex); const baselineCallIndex = source.indexOf("establishInitialBaseline();", initializationGuardIndex); const pausedReturnIndex = source.indexOf("return () =>", baselineCallIndex); assert.notEqual(pausedGuardIndex, -1); assert.notEqual(initializationGuardIndex, -1); assert.notEqual(baselineCallIndex, -1); assert.notEqual(pausedReturnIndex, -1); assert.ok( pausedGuardIndex < initializationGuardIndex && initializationGuardIndex < baselineCallIndex && baselineCallIndex < pausedReturnIndex, "paused mode must preserve the pre-edit baseline before suppressing network sync", ); }); test("an unchanged remote check cannot absorb an existing paused-edit baseline", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const baselineSnapshotIndex = source.indexOf("const hadInitialBaseline = isInitializedRef.current"); const remoteChangeIndex = source.indexOf("inspectedRemoteChange = true", baselineSnapshotIndex); const guardedUpdateIndex = source.indexOf( "if (markCurrentDataSynced && (!hadInitialBaseline || inspectedRemoteChange))", remoteChangeIndex, ); assert.notEqual(baselineSnapshotIndex, -1); assert.notEqual(remoteChangeIndex, -1); assert.notEqual(guardedUpdateIndex, -1); assert.ok( baselineSnapshotIndex < remoteChangeIndex && remoteChangeIndex < guardedUpdateIndex, "remote inspection must preserve an existing baseline unless it reconciled changed cloud data", ); }); test("enabled convergent remote checks use the CRDT runtime before legacy inspection", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const checkIndex = source.indexOf("const checkRemoteVersion = useCallback"); const convergentGuardIndex = source.indexOf( "if (currentConvergentConfig.initialized && currentConvergentConfig.enabled)", checkIndex, ); const convergentSyncIndex = source.indexOf( "syncNowRef.current({ notifyOnFailure, allowEmptyConvergentSync })", convergentGuardIndex, ); const legacyInspectionIndex = source.indexOf( "manager.inspectProviderRemote(connectedProvider)", convergentSyncIndex, ); assert.notEqual(checkIndex, -1); assert.notEqual(convergentGuardIndex, -1); assert.notEqual(convergentSyncIndex, -1); assert.notEqual(legacyInspectionIndex, -1); assert.ok( convergentGuardIndex < convergentSyncIndex && convergentSyncIndex < legacyInspectionIndex, "v2 checks must return through the CRDT runtime before the legacy snapshot inspector", ); }); test("enabled convergent startup checks preview empty-vault recovery before syncing", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const convergentGuardIndex = source.indexOf( "if (currentConvergentConfig.initialized && currentConvergentConfig.enabled)", ); const previewIndex = source.indexOf("manager.previewConvergentRecovery()", convergentGuardIndex); const recoveryPromptIndex = source.indexOf( "requestEmptyVaultRecovery(recoveryPayload)", previewIndex, ); const cloudWinsIndex = source.indexOf( "conflictActionOverride: 'download-remote'", recoveryPromptIndex, ); const manualTriggerIndex = source.indexOf("trigger: 'manual'", recoveryPromptIndex); const normalSyncIndex = source.indexOf( "syncNowRef.current({ notifyOnFailure, allowEmptyConvergentSync })", cloudWinsIndex, ); assert.notEqual(convergentGuardIndex, -1); assert.notEqual(previewIndex, -1); assert.notEqual(recoveryPromptIndex, -1); assert.notEqual(cloudWinsIndex, -1); assert.notEqual(manualTriggerIndex, -1); assert.notEqual(normalSyncIndex, -1); assert.ok( convergentGuardIndex < previewIndex && previewIndex < recoveryPromptIndex && recoveryPromptIndex < manualTriggerIndex && manualTriggerIndex < cloudWinsIndex && cloudWinsIndex < normalSyncIndex, "v2 startup must offer recovery and use cloud-wins before the normal CRDT sync path", ); }); test("empty-vault recovery restore bypasses the auto-sync preference gate", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const recoveryPromptIndex = source.indexOf("requestEmptyVaultRecovery(recoveryPayload)"); const restoreCallIndex = source.indexOf("syncNowRef.current({", recoveryPromptIndex); const triggerIndex = source.indexOf("trigger: 'manual'", restoreCallIndex); const conflictIndex = source.indexOf( "conflictActionOverride: 'download-remote'", restoreCallIndex, ); const callEndIndex = source.indexOf("});", conflictIndex); assert.notEqual(recoveryPromptIndex, -1); assert.notEqual(restoreCallIndex, -1); assert.notEqual(triggerIndex, -1); assert.notEqual(conflictIndex, -1); assert.ok( restoreCallIndex < triggerIndex && triggerIndex < conflictIndex && conflictIndex < callEndIndex, "recovery Restore must pass trigger:manual so disabled auto-sync cannot block it", ); }); test("empty v2 startup checks allow a validated empty sync to open the gate", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const convergentGuardIndex = source.indexOf( "if (currentConvergentConfig.initialized && currentConvergentConfig.enabled)", ); const replicaIndex = source.indexOf("manager.loadConvergentReplica()", convergentGuardIndex); const emptyDecisionIndex = source.indexOf("allowEmptyConvergentSync = (", replicaIndex); const syncIndex = source.indexOf( "syncNowRef.current({ notifyOnFailure, allowEmptyConvergentSync })", emptyDecisionIndex, ); const guardIndex = source.indexOf( "options?.allowEmptyConvergentSync !== true", source.indexOf("const syncNow = useCallback"), ); assert.notEqual(replicaIndex, -1); assert.notEqual(emptyDecisionIndex, -1); assert.notEqual(syncIndex, -1); assert.notEqual(guardIndex, -1); assert.ok(replicaIndex < emptyDecisionIndex && emptyDecisionIndex < syncIndex); }); test("auto-sync skips only the exact remote-applied data hash", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const helperIndex = source.indexOf("const getSyncPayloadDataHash = (payload: SyncPayload): string"); const skipRefIndex = source.indexOf("const skipNextSyncHashRef = useRef(null)"); const assignmentIndex = source.indexOf("skipNextSyncHashRef.current = getSyncPayloadDataHash(remotePayload)"); const debounceTimerIndex = source.indexOf("syncTimeoutRef.current = setTimeout", assignmentIndex); const skipHashIndex = source.indexOf("const skipHash = skipNextSyncHashRef.current", debounceTimerIndex); const decisionIndex = source.indexOf("resolveAutoSyncHashDecision({", skipHashIndex); const appliedSkipIndex = source.indexOf("appliedSkipHash: skipHash", decisionIndex); const syncingGuardIndex = source.indexOf("if (sync.isSyncing || isSyncRunningRef.current)", decisionIndex); const restoreGuardIndex = source.indexOf("if (isRestoreInProgress())", decisionIndex); const interruptedGuardIndex = source.indexOf("if (readInterruptedVaultApply())", decisionIndex); const syncNowIndex = source.indexOf("const didSync = await syncNow();", interruptedGuardIndex); const didSyncGuardIndex = source.indexOf("if (didSync && skipHash !== null", syncNowIndex); const clearAfterSyncIndex = source.indexOf("skipNextSyncHashRef.current = null;", didSyncGuardIndex); const booleanSkipIndex = source.indexOf("skipNextSyncRef"); assert.notEqual(helperIndex, -1); assert.notEqual(skipRefIndex, -1); assert.notEqual(assignmentIndex, -1); assert.notEqual(debounceTimerIndex, -1); assert.notEqual(skipHashIndex, -1); assert.notEqual(decisionIndex, -1); assert.notEqual(appliedSkipIndex, -1); assert.notEqual(syncingGuardIndex, -1); assert.notEqual(restoreGuardIndex, -1); assert.notEqual(interruptedGuardIndex, -1); assert.notEqual(syncNowIndex, -1); assert.notEqual(didSyncGuardIndex, -1); assert.notEqual(clearAfterSyncIndex, -1); assert.equal(booleanSkipIndex, -1); assert.ok( skipHashIndex < decisionIndex, "remote-apply skip must pass through the hash decision helper before suppressing a sync", ); assert.ok( interruptedGuardIndex < syncNowIndex && syncNowIndex < clearAfterSyncIndex, "remote-apply skip hash must survive temporary sync blockers and clear only after a successful sync", ); }); test("auto syncNow and debounce re-check manager autoSyncEnabled before pushing", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const helperIndex = source.indexOf("function isPersistedAutoSyncEnabled"); const syncNowIndex = source.indexOf("const syncNow = useCallback"); const autoGateIndex = source.indexOf( "if (trigger === 'auto' && !isPersistedAutoSyncEnabled(manager.getState().autoSyncEnabled))", syncNowIndex, ); const debounceIndex = source.indexOf("// Debounced auto-sync when data changes"); const fireTimeGateIndex = source.indexOf( "if (!isPersistedAutoSyncEnabled(manager.getState().autoSyncEnabled))", debounceIndex, ); const clearTimerIndex = source.indexOf( "if (syncTimeoutRef.current) {\n clearTimeout(syncTimeoutRef.current);\n syncTimeoutRef.current = null;\n }\n return;", debounceIndex, ); assert.notEqual(helperIndex, -1); assert.notEqual(syncNowIndex, -1); assert.notEqual(autoGateIndex, -1); assert.notEqual(fireTimeGateIndex, -1); assert.notEqual(clearTimerIndex, -1); assert.ok( helperIndex < syncNowIndex && syncNowIndex < autoGateIndex && debounceIndex < fireTimeGateIndex && debounceIndex < clearTimerIndex, "auto-sync off must gate both syncNow(auto) and the debounced timer path", ); }); test("startup local-wins and merge round-trips refuse device-bound credential placeholders", () => { const source = readFileSync(new URL("./useAutoSync.ts", import.meta.url), "utf8"); const uploadLocalIndex = source.indexOf("if (conflictAction === 'upload-local')"); const uploadGuardIndex = source.indexOf( "findSyncPayloadEncryptedCredentialPaths(localPayload)", uploadLocalIndex, ); const uploadPushIndex = source.indexOf("manager.syncAllProviders(localPayload)", uploadGuardIndex); const mergeRoundTripBlockIndex = source.indexOf( "stripSyncPayloadEncryptedCredentials(mergeResult.payload)", ); const mergeLocalHealIndex = source.indexOf( "healPoisonedSecretsForMerge(localPayload, remoteRaw, base)", ); const mergeRemoteHealIndex = source.indexOf( "healPoisonedSecretsForMerge(remoteRaw, localPayload, base)", ); const mergePushIndex = source.indexOf( "manager.syncAllProviders(portableMerge)", mergeRoundTripBlockIndex, ); const stripRemoteIndex = source.indexOf( "stripSyncPayloadEncryptedCredentials(remoteRaw)", ); assert.notEqual(uploadLocalIndex, -1); assert.notEqual(uploadGuardIndex, -1); assert.notEqual(uploadPushIndex, -1); assert.notEqual(mergeLocalHealIndex, -1); assert.notEqual(mergeRemoteHealIndex, -1); assert.notEqual(mergeRoundTripBlockIndex, -1); assert.notEqual(mergePushIndex, -1); assert.notEqual(stripRemoteIndex, -1); assert.ok( uploadGuardIndex < uploadPushIndex, "startup local-wins must guard placeholders before syncAllProviders", ); assert.ok( mergeLocalHealIndex < mergeRoundTripBlockIndex && mergeRemoteHealIndex < mergeRoundTripBlockIndex && mergeRoundTripBlockIndex < mergePushIndex, "startup merge must heal local+remote secrets, then strip, then upload", ); });