Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
436 lines
13 KiB
TypeScript
436 lines
13 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { SYNC_STORAGE_KEYS } from '../../../domain/sync.ts';
|
|
import { saveSyncConfigImpl, setAutoSyncImpl } from './syncAllStorageMethods.ts';
|
|
|
|
test('version saveSyncConfig does not clobber another window autoSync=false', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_PREFERENCES, {
|
|
autoSync: false,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
localVersion: 3,
|
|
localUpdatedAt: 100,
|
|
remoteVersion: 3,
|
|
remoteUpdatedAt: 100,
|
|
});
|
|
|
|
let stopCount = 0;
|
|
const manager = {
|
|
state: {
|
|
autoSyncEnabled: true, // stale — this window has not observed the other window yet
|
|
autoSyncInterval: 5,
|
|
localVersion: 4,
|
|
localUpdatedAt: 200,
|
|
remoteVersion: 4,
|
|
remoteUpdatedAt: 200,
|
|
syncStrategy: 'smartMerge',
|
|
securityState: 'UNLOCKED',
|
|
},
|
|
loadFromStorage(key: string) {
|
|
return storage.get(key) ?? null;
|
|
},
|
|
saveToStorage(key: string, value: unknown) {
|
|
storage.set(key, value);
|
|
return true;
|
|
},
|
|
saveSyncConfig() {
|
|
saveSyncConfigImpl.call(this);
|
|
},
|
|
notifyStateChange() {},
|
|
startAutoSync() {},
|
|
stopAutoSync() {
|
|
stopCount += 1;
|
|
},
|
|
};
|
|
|
|
// Simulate post-upload version bump save (default: preferences from storage)
|
|
saveSyncConfigImpl.call(manager);
|
|
|
|
const savedConfig = storage.get(SYNC_STORAGE_KEYS.SYNC_CONFIG) as {
|
|
autoSync?: boolean;
|
|
localVersion: number;
|
|
};
|
|
const savedPrefs = storage.get(SYNC_STORAGE_KEYS.SYNC_PREFERENCES) as { autoSync: boolean };
|
|
assert.equal(savedPrefs.autoSync, false, 'must not re-enable auto-sync from stale memory');
|
|
assert.equal(savedConfig.autoSync, undefined, 'version blob must omit preference fields');
|
|
assert.equal(savedConfig.localVersion, 4);
|
|
assert.equal(manager.state.autoSyncEnabled, false, 'memory should adopt storage preference');
|
|
assert.equal(stopCount, 1);
|
|
});
|
|
|
|
test('version save after mid-flight preference toggle keeps autoSync=false', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_PREFERENCES, {
|
|
autoSync: true,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
localVersion: 3,
|
|
localUpdatedAt: 100,
|
|
remoteVersion: 3,
|
|
remoteUpdatedAt: 100,
|
|
});
|
|
|
|
let preferenceReads = 0;
|
|
const manager = {
|
|
state: {
|
|
autoSyncEnabled: true,
|
|
autoSyncInterval: 5,
|
|
localVersion: 4,
|
|
localUpdatedAt: 200,
|
|
remoteVersion: 4,
|
|
remoteUpdatedAt: 200,
|
|
syncStrategy: 'smartMerge',
|
|
securityState: 'UNLOCKED',
|
|
},
|
|
loadFromStorage(key: string) {
|
|
if (key === SYNC_STORAGE_KEYS.SYNC_PREFERENCES) {
|
|
preferenceReads += 1;
|
|
// First existence check still sees autoSync=true; a Settings toggle
|
|
// lands before the post-write re-read used for memory adoption.
|
|
if (preferenceReads === 1) {
|
|
return storage.get(key) ?? null;
|
|
}
|
|
return {
|
|
autoSync: false,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
};
|
|
}
|
|
return storage.get(key) ?? null;
|
|
},
|
|
saveToStorage(key: string, value: unknown) {
|
|
storage.set(key, value);
|
|
// Concurrent Settings window disables auto-sync while versions persist.
|
|
if (key === SYNC_STORAGE_KEYS.SYNC_CONFIG) {
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_PREFERENCES, {
|
|
autoSync: false,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
}
|
|
return true;
|
|
},
|
|
notifyStateChange() {},
|
|
startAutoSync() {},
|
|
stopAutoSync() {},
|
|
};
|
|
|
|
saveSyncConfigImpl.call(manager);
|
|
|
|
const savedPrefs = storage.get(SYNC_STORAGE_KEYS.SYNC_PREFERENCES) as { autoSync: boolean };
|
|
const savedConfig = storage.get(SYNC_STORAGE_KEYS.SYNC_CONFIG) as { autoSync?: boolean };
|
|
assert.equal(savedPrefs.autoSync, false);
|
|
assert.equal(savedConfig.autoSync, undefined);
|
|
assert.equal(manager.state.autoSyncEnabled, false);
|
|
});
|
|
|
|
test('setAutoSync still persists the explicit preference from memory', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
localVersion: 1,
|
|
localUpdatedAt: 1,
|
|
remoteVersion: 1,
|
|
remoteUpdatedAt: 1,
|
|
});
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_PREFERENCES, {
|
|
autoSync: true,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
|
|
const manager = {
|
|
state: {
|
|
autoSyncEnabled: true,
|
|
autoSyncInterval: 5,
|
|
localVersion: 1,
|
|
localUpdatedAt: 1,
|
|
remoteVersion: 1,
|
|
remoteUpdatedAt: 1,
|
|
syncStrategy: 'smartMerge',
|
|
securityState: 'UNLOCKED',
|
|
},
|
|
loadFromStorage(key: string) {
|
|
return storage.get(key) ?? null;
|
|
},
|
|
saveToStorage(key: string, value: unknown) {
|
|
storage.set(key, value);
|
|
return true;
|
|
},
|
|
saveSyncConfig(opts?: {
|
|
preferencesFromMemory?: boolean;
|
|
memoryKeys?: ReadonlyArray<'autoSync' | 'interval' | 'syncStrategy'>;
|
|
}) {
|
|
saveSyncConfigImpl.call(this, opts);
|
|
},
|
|
notifyStateChange() {},
|
|
startAutoSync() {},
|
|
stopAutoSync() {},
|
|
};
|
|
|
|
setAutoSyncImpl.call(manager, false);
|
|
|
|
const savedPrefs = storage.get(SYNC_STORAGE_KEYS.SYNC_PREFERENCES) as { autoSync: boolean };
|
|
const savedConfig = storage.get(SYNC_STORAGE_KEYS.SYNC_CONFIG) as { autoSync?: boolean };
|
|
assert.equal(savedPrefs.autoSync, false);
|
|
assert.equal(savedConfig.autoSync, undefined);
|
|
assert.equal(manager.state.autoSyncEnabled, false);
|
|
});
|
|
|
|
test('strategy preference write does not re-enable stale autoSync from memory', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_PREFERENCES, {
|
|
autoSync: false,
|
|
interval: 15,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
localVersion: 2,
|
|
localUpdatedAt: 20,
|
|
remoteVersion: 2,
|
|
remoteUpdatedAt: 20,
|
|
});
|
|
|
|
const manager = {
|
|
state: {
|
|
// Stale: another window already disabled auto-sync in storage.
|
|
autoSyncEnabled: true,
|
|
autoSyncInterval: 5,
|
|
localVersion: 2,
|
|
localUpdatedAt: 20,
|
|
remoteVersion: 2,
|
|
remoteUpdatedAt: 20,
|
|
syncStrategy: 'preferLocal',
|
|
securityState: 'UNLOCKED',
|
|
},
|
|
loadFromStorage(key: string) {
|
|
return storage.get(key) ?? null;
|
|
},
|
|
saveToStorage(key: string, value: unknown) {
|
|
storage.set(key, value);
|
|
return true;
|
|
},
|
|
saveSyncConfig(opts?: {
|
|
preferencesFromMemory?: boolean;
|
|
memoryKeys?: ReadonlyArray<'autoSync' | 'interval' | 'syncStrategy'>;
|
|
}) {
|
|
saveSyncConfigImpl.call(this, opts);
|
|
},
|
|
notifyStateChange() {},
|
|
startAutoSync() {},
|
|
stopAutoSync() {},
|
|
};
|
|
|
|
saveSyncConfigImpl.call(manager, {
|
|
preferencesFromMemory: true,
|
|
memoryKeys: ['syncStrategy'],
|
|
});
|
|
|
|
const savedPrefs = storage.get(SYNC_STORAGE_KEYS.SYNC_PREFERENCES) as {
|
|
autoSync: boolean;
|
|
interval: number;
|
|
syncStrategy: string;
|
|
};
|
|
assert.equal(savedPrefs.autoSync, false);
|
|
assert.equal(savedPrefs.interval, 15);
|
|
assert.equal(savedPrefs.syncStrategy, 'preferLocal');
|
|
});
|
|
|
|
function createPersistHarness(storage: Map<string, unknown>, state: {
|
|
autoSyncEnabled: boolean;
|
|
autoSyncInterval: number;
|
|
localVersion: number;
|
|
localUpdatedAt: number;
|
|
remoteVersion: number;
|
|
remoteUpdatedAt: number;
|
|
syncStrategy: string;
|
|
securityState?: string;
|
|
}) {
|
|
return {
|
|
state: {
|
|
securityState: 'UNLOCKED',
|
|
...state,
|
|
},
|
|
loadFromStorage(key: string) {
|
|
return storage.get(key) ?? null;
|
|
},
|
|
saveToStorage(key: string, value: unknown) {
|
|
storage.set(key, value);
|
|
return true;
|
|
},
|
|
saveSyncConfig(opts?: {
|
|
preferencesFromMemory?: boolean;
|
|
memoryKeys?: ReadonlyArray<'autoSync' | 'interval' | 'syncStrategy'>;
|
|
}) {
|
|
saveSyncConfigImpl.call(this, opts);
|
|
},
|
|
notifyStateChange() {},
|
|
startAutoSync() {},
|
|
stopAutoSync() {},
|
|
};
|
|
}
|
|
|
|
test('version-only save does not create SYNC_PREFERENCES during first upgrade', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
autoSync: true,
|
|
interval: 15,
|
|
syncStrategy: 'preferCloud',
|
|
localVersion: 3,
|
|
localUpdatedAt: 100,
|
|
remoteVersion: 3,
|
|
remoteUpdatedAt: 100,
|
|
});
|
|
|
|
const manager = createPersistHarness(storage, {
|
|
autoSyncEnabled: true,
|
|
autoSyncInterval: 15,
|
|
localVersion: 4,
|
|
localUpdatedAt: 200,
|
|
remoteVersion: 4,
|
|
remoteUpdatedAt: 200,
|
|
syncStrategy: 'preferCloud',
|
|
});
|
|
|
|
saveSyncConfigImpl.call(manager);
|
|
|
|
assert.equal(storage.has(SYNC_STORAGE_KEYS.SYNC_PREFERENCES), false);
|
|
const savedConfig = storage.get(SYNC_STORAGE_KEYS.SYNC_CONFIG) as {
|
|
autoSync?: boolean;
|
|
interval?: number;
|
|
syncStrategy?: string;
|
|
localVersion: number;
|
|
};
|
|
assert.equal(savedConfig.autoSync, true);
|
|
assert.equal(savedConfig.interval, 15);
|
|
assert.equal(savedConfig.syncStrategy, 'preferCloud');
|
|
assert.equal(savedConfig.localVersion, 4);
|
|
});
|
|
|
|
test('version-only save preserves stored legacy autoSync=false over stale memory', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
autoSync: false,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
localVersion: 3,
|
|
localUpdatedAt: 100,
|
|
remoteVersion: 3,
|
|
remoteUpdatedAt: 100,
|
|
});
|
|
|
|
const manager = createPersistHarness(storage, {
|
|
autoSyncEnabled: true,
|
|
autoSyncInterval: 5,
|
|
localVersion: 4,
|
|
localUpdatedAt: 200,
|
|
remoteVersion: 4,
|
|
remoteUpdatedAt: 200,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
|
|
saveSyncConfigImpl.call(manager);
|
|
|
|
assert.equal(storage.has(SYNC_STORAGE_KEYS.SYNC_PREFERENCES), false);
|
|
const savedConfig = storage.get(SYNC_STORAGE_KEYS.SYNC_CONFIG) as { autoSync?: boolean };
|
|
assert.equal(savedConfig.autoSync, false);
|
|
assert.equal(manager.state.autoSyncEnabled, false);
|
|
});
|
|
|
|
test('version-only save cannot overwrite a concurrent first-upgrade autoSync=false', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
autoSync: true,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
localVersion: 3,
|
|
localUpdatedAt: 100,
|
|
remoteVersion: 3,
|
|
remoteUpdatedAt: 100,
|
|
});
|
|
|
|
let preferenceReads = 0;
|
|
const manager = {
|
|
...createPersistHarness(storage, {
|
|
autoSyncEnabled: true,
|
|
autoSyncInterval: 5,
|
|
localVersion: 4,
|
|
localUpdatedAt: 200,
|
|
remoteVersion: 4,
|
|
remoteUpdatedAt: 200,
|
|
syncStrategy: 'smartMerge',
|
|
}),
|
|
loadFromStorage(key: string) {
|
|
if (key === SYNC_STORAGE_KEYS.SYNC_PREFERENCES) {
|
|
preferenceReads += 1;
|
|
// Settings toggle lands after the existence check and before the
|
|
// post-write reread — the previous migrate wrote stale autoSync=true
|
|
// into this key and clobbered the toggle.
|
|
if (preferenceReads === 1) return null;
|
|
return storage.get(key) ?? null;
|
|
}
|
|
return storage.get(key) ?? null;
|
|
},
|
|
saveToStorage(key: string, value: unknown) {
|
|
storage.set(key, value);
|
|
if (key === SYNC_STORAGE_KEYS.SYNC_CONFIG) {
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_PREFERENCES, {
|
|
autoSync: false,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
}
|
|
return true;
|
|
},
|
|
};
|
|
|
|
saveSyncConfigImpl.call(manager);
|
|
|
|
const savedPrefs = storage.get(SYNC_STORAGE_KEYS.SYNC_PREFERENCES) as { autoSync: boolean };
|
|
assert.equal(savedPrefs.autoSync, false, 'must not migrate stale autoSync=true over a concurrent toggle');
|
|
assert.equal(manager.state.autoSyncEnabled, false);
|
|
});
|
|
|
|
test('first-upgrade setAutoSync(false) then version save does not re-enable', () => {
|
|
const storage = new Map<string, unknown>();
|
|
storage.set(SYNC_STORAGE_KEYS.SYNC_CONFIG, {
|
|
autoSync: true,
|
|
interval: 5,
|
|
syncStrategy: 'smartMerge',
|
|
localVersion: 3,
|
|
localUpdatedAt: 100,
|
|
remoteVersion: 3,
|
|
remoteUpdatedAt: 100,
|
|
});
|
|
|
|
const manager = createPersistHarness(storage, {
|
|
autoSyncEnabled: true,
|
|
autoSyncInterval: 5,
|
|
localVersion: 3,
|
|
localUpdatedAt: 100,
|
|
remoteVersion: 3,
|
|
remoteUpdatedAt: 100,
|
|
syncStrategy: 'smartMerge',
|
|
});
|
|
|
|
setAutoSyncImpl.call(manager, false);
|
|
manager.state.localVersion = 4;
|
|
manager.state.localUpdatedAt = 200;
|
|
saveSyncConfigImpl.call(manager);
|
|
|
|
const savedPrefs = storage.get(SYNC_STORAGE_KEYS.SYNC_PREFERENCES) as { autoSync: boolean };
|
|
const savedConfig = storage.get(SYNC_STORAGE_KEYS.SYNC_CONFIG) as {
|
|
autoSync?: boolean;
|
|
localVersion: number;
|
|
};
|
|
assert.equal(savedPrefs.autoSync, false);
|
|
assert.equal(savedConfig.autoSync, undefined);
|
|
assert.equal(savedConfig.localVersion, 4);
|
|
assert.equal(manager.state.autoSyncEnabled, false);
|
|
});
|