mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 04:49:49 -07:00
ffa183b1a1
The Electron app now answers launcher-style sync argv (--sync-cli sync [host|--snapshot|--merge] ... plus --help/--version) at entry, before any window or display initialization, backed by a libsql binding of the shared stats-sync engine. Works over SSH with no display server, so a remote machine no longer needs the bun launcher. The launcher accepts --sync-cli as a no-op so both invocation shapes stay equivalent.
77 lines
2.9 KiB
TypeScript
77 lines
2.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 {
|
|
createImmersionDbFixture,
|
|
insertFixtureSession,
|
|
} from '../test-support/immersion-db-fixture.js';
|
|
import { openLibsqlSyncDb } from '../../src/core/services/stats-sync/libsql-driver.js';
|
|
import { createDbSnapshot } from '../../src/core/services/stats-sync/shared.js';
|
|
import { mergeSnapshotIntoDb } from '../../src/core/services/stats-sync/merge.js';
|
|
|
|
const BASE_MS = Date.UTC(2026, 5, 1, 12, 0, 0);
|
|
|
|
function makeTmpDir(): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-libsql-sync-test-'));
|
|
}
|
|
|
|
function countRows(dbPath: string, sql: string): number {
|
|
const db = openLibsqlSyncDb(dbPath, { readonly: true });
|
|
try {
|
|
const row = db.query(sql).get() as { n: number } | undefined;
|
|
return Number(row?.n ?? 0);
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
// End-to-end pass of the shared engine over the libsql driver (the binding the
|
|
// Electron app's --sync-cli mode uses): snapshot a fixture DB, merge it into
|
|
// another, and re-merge to confirm idempotence.
|
|
test('libsql driver runs snapshot and merge through the shared engine', () => {
|
|
const dir = makeTmpDir();
|
|
try {
|
|
const localPath = path.join(dir, 'local.sqlite');
|
|
const remotePath = path.join(dir, 'remote.sqlite');
|
|
createImmersionDbFixture(localPath);
|
|
createImmersionDbFixture(remotePath);
|
|
insertFixtureSession(localPath, {
|
|
uuid: 'local-1',
|
|
videoKey: 'showa-e1',
|
|
animeTitleKey: 'showa',
|
|
startedAtMs: BASE_MS,
|
|
applyLifetime: true,
|
|
words: [{ headword: '見る', word: '見た', reading: 'みた', count: 2 }],
|
|
});
|
|
insertFixtureSession(remotePath, {
|
|
uuid: 'remote-1',
|
|
videoKey: 'showb-e1',
|
|
animeTitleKey: 'showb',
|
|
startedAtMs: BASE_MS + 86_400_000,
|
|
activeWatchedMs: 900_000,
|
|
cardsMined: 3,
|
|
applyLifetime: true,
|
|
words: [{ headword: '食べる', word: '食べた', reading: 'たべた', count: 1 }],
|
|
});
|
|
|
|
const snapshotPath = path.join(dir, 'remote-snapshot.sqlite');
|
|
createDbSnapshot(openLibsqlSyncDb, remotePath, snapshotPath);
|
|
assert.ok(fs.existsSync(snapshotPath));
|
|
assert.equal(countRows(snapshotPath, 'SELECT COUNT(*) AS n FROM imm_sessions'), 1);
|
|
|
|
const summary = mergeSnapshotIntoDb(openLibsqlSyncDb, localPath, snapshotPath);
|
|
assert.equal(summary.sessionsMerged, 1);
|
|
assert.equal(summary.videosAdded, 1);
|
|
assert.equal(countRows(localPath, 'SELECT COUNT(*) AS n FROM imm_sessions'), 2);
|
|
|
|
const again = mergeSnapshotIntoDb(openLibsqlSyncDb, localPath, snapshotPath);
|
|
assert.equal(again.sessionsMerged, 0);
|
|
assert.equal(again.sessionsAlreadyPresent, 1);
|
|
assert.equal(countRows(localPath, 'SELECT COUNT(*) AS n FROM imm_sessions'), 2);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|