mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 04:49:49 -07:00
183 lines
5.9 KiB
TypeScript
183 lines
5.9 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import {
|
|
createDefaultSyncHostsState,
|
|
getSyncHostsPath,
|
|
isValidSyncHost,
|
|
normalizeSyncHostsState,
|
|
readSyncHostsState,
|
|
recordSyncResult,
|
|
removeSyncHost,
|
|
upsertSyncHost,
|
|
writeSyncHostsState,
|
|
} from './sync-hosts-store';
|
|
|
|
function withTempDir(fn: (dir: string) => void): void {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-sync-hosts-test-'));
|
|
try {
|
|
fn(dir);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test('createDefaultSyncHostsState returns empty v1 state', () => {
|
|
assert.deepEqual(createDefaultSyncHostsState(), {
|
|
version: 1,
|
|
autoSyncIntervalMinutes: 30,
|
|
hosts: [],
|
|
});
|
|
});
|
|
|
|
test('isValidSyncHost accepts ssh destinations and rejects unsafe values', () => {
|
|
assert.equal(isValidSyncHost('desktop'), true);
|
|
assert.equal(isValidSyncHost('user@10.0.0.5'), true);
|
|
assert.equal(isValidSyncHost('my-alias.local'), true);
|
|
assert.equal(isValidSyncHost(''), false);
|
|
assert.equal(isValidSyncHost(' '), false);
|
|
assert.equal(isValidSyncHost('-oProxyCommand=evil'), false);
|
|
assert.equal(isValidSyncHost('host with spaces'), false);
|
|
});
|
|
|
|
test('upsertSyncHost adds a new host with defaults and preserves fields on update', () => {
|
|
const state = createDefaultSyncHostsState();
|
|
const added = upsertSyncHost(state, { host: 'user@laptop' }, 1000);
|
|
assert.deepEqual(added.hosts, [
|
|
{
|
|
host: 'user@laptop',
|
|
label: null,
|
|
direction: 'both',
|
|
autoSync: false,
|
|
addedAtMs: 1000,
|
|
lastSyncAtMs: null,
|
|
lastSyncStatus: null,
|
|
lastSyncDetail: null,
|
|
},
|
|
]);
|
|
// original state untouched
|
|
assert.equal(state.hosts.length, 0);
|
|
|
|
const synced = recordSyncResult(added, 'user@laptop', {
|
|
atMs: 2000,
|
|
status: 'success',
|
|
detail: 'Sessions merged: 3',
|
|
});
|
|
const updated = upsertSyncHost(synced, { host: 'user@laptop', direction: 'push' }, 3000);
|
|
assert.equal(updated.hosts.length, 1);
|
|
const entry = updated.hosts[0]!;
|
|
assert.equal(entry.direction, 'push');
|
|
assert.equal(entry.addedAtMs, 1000);
|
|
assert.equal(entry.lastSyncAtMs, 2000);
|
|
assert.equal(entry.lastSyncStatus, 'success');
|
|
assert.equal(entry.lastSyncDetail, 'Sessions merged: 3');
|
|
});
|
|
|
|
test('upsertSyncHost trims host and throws on invalid host', () => {
|
|
const state = upsertSyncHost(createDefaultSyncHostsState(), { host: ' desktop ' }, 1);
|
|
assert.equal(state.hosts[0]!.host, 'desktop');
|
|
assert.throws(() => upsertSyncHost(state, { host: '-bad' }, 2));
|
|
});
|
|
|
|
test('removeSyncHost removes only the matching host', () => {
|
|
let state = createDefaultSyncHostsState();
|
|
state = upsertSyncHost(state, { host: 'a' }, 1);
|
|
state = upsertSyncHost(state, { host: 'b' }, 2);
|
|
const removed = removeSyncHost(state, 'a');
|
|
assert.deepEqual(
|
|
removed.hosts.map((entry) => entry.host),
|
|
['b'],
|
|
);
|
|
assert.equal(removeSyncHost(removed, 'missing').hosts.length, 1);
|
|
});
|
|
|
|
test('recordSyncResult upserts unknown hosts so CLI syncs are remembered', () => {
|
|
const state = recordSyncResult(createDefaultSyncHostsState(), 'user@server', {
|
|
atMs: 5000,
|
|
status: 'error',
|
|
detail: 'Remote merge failed',
|
|
});
|
|
assert.equal(state.hosts.length, 1);
|
|
const entry = state.hosts[0]!;
|
|
assert.equal(entry.host, 'user@server');
|
|
assert.equal(entry.addedAtMs, 5000);
|
|
assert.equal(entry.lastSyncAtMs, 5000);
|
|
assert.equal(entry.lastSyncStatus, 'error');
|
|
assert.equal(entry.lastSyncDetail, 'Remote merge failed');
|
|
});
|
|
|
|
test('normalizeSyncHostsState drops invalid entries and falls back to defaults', () => {
|
|
assert.deepEqual(normalizeSyncHostsState(null), createDefaultSyncHostsState());
|
|
assert.deepEqual(normalizeSyncHostsState('junk'), createDefaultSyncHostsState());
|
|
assert.deepEqual(normalizeSyncHostsState({ version: 99 }), createDefaultSyncHostsState());
|
|
|
|
const normalized = normalizeSyncHostsState({
|
|
version: 1,
|
|
autoSyncIntervalMinutes: 15,
|
|
hosts: [
|
|
{
|
|
host: 'desktop',
|
|
label: 'Desktop PC',
|
|
direction: 'pull',
|
|
autoSync: true,
|
|
addedAtMs: 10,
|
|
lastSyncAtMs: 20,
|
|
lastSyncStatus: 'success',
|
|
lastSyncDetail: 'ok',
|
|
},
|
|
{ host: '-bad', direction: 'both', autoSync: false, addedAtMs: 1 },
|
|
{ host: 'dupe', direction: 'sideways', autoSync: false, addedAtMs: 1 },
|
|
'not-an-object',
|
|
],
|
|
});
|
|
assert.equal(normalized.autoSyncIntervalMinutes, 15);
|
|
assert.deepEqual(normalized.hosts, [
|
|
{
|
|
host: 'desktop',
|
|
label: 'Desktop PC',
|
|
direction: 'pull',
|
|
autoSync: true,
|
|
addedAtMs: 10,
|
|
lastSyncAtMs: 20,
|
|
lastSyncStatus: 'success',
|
|
lastSyncDetail: 'ok',
|
|
},
|
|
{
|
|
host: 'dupe',
|
|
label: null,
|
|
direction: 'both',
|
|
autoSync: false,
|
|
addedAtMs: 1,
|
|
lastSyncAtMs: null,
|
|
lastSyncStatus: null,
|
|
lastSyncDetail: null,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('read/write round-trips state and tolerates missing or corrupt files', () => {
|
|
withTempDir((root) => {
|
|
const filePath = getSyncHostsPath(root);
|
|
assert.equal(filePath, path.join(root, 'sync-hosts.json'));
|
|
assert.deepEqual(readSyncHostsState(filePath), createDefaultSyncHostsState());
|
|
|
|
fs.writeFileSync(filePath, '{corrupt');
|
|
assert.deepEqual(readSyncHostsState(filePath), createDefaultSyncHostsState());
|
|
|
|
let state = createDefaultSyncHostsState();
|
|
state = upsertSyncHost(state, { host: 'user@laptop', label: 'Laptop' }, 42);
|
|
writeSyncHostsState(filePath, state);
|
|
assert.deepEqual(readSyncHostsState(filePath), state);
|
|
});
|
|
});
|
|
|
|
test('writeSyncHostsState creates parent directories', () => {
|
|
withTempDir((root) => {
|
|
const filePath = path.join(root, 'nested', 'dir', 'sync-hosts.json');
|
|
writeSyncHostsState(filePath, createDefaultSyncHostsState());
|
|
assert.deepEqual(readSyncHostsState(filePath), createDefaultSyncHostsState());
|
|
});
|
|
});
|