mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-10 00:10:13 +00:00
Port new tests
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"tasks": {
|
||||
"test": "deno test --env-file=.test.env -A --no-check test-*.ts",
|
||||
"test:local": "deno test --env-file=.test.env -A --no-check test-setup-put-cat.ts test-mirror.ts",
|
||||
"test:local": "deno test --env-file=.test.env -A --no-check test-setup-put-cat.ts test-mirror.ts test-daemon.ts",
|
||||
"test:daemon": "deno test --env-file=.test.env -A --no-check test-daemon.ts",
|
||||
"test:decoupled-vault": "deno test --env-file=.test.env -A --no-check test-decoupled-vault.ts",
|
||||
"test:remote-commands": "deno test --env-file=.test.env -A --no-check test-remote-commands.ts",
|
||||
"test:push-pull": "deno test --env-file=.test.env -A --no-check test-push-pull.ts",
|
||||
"test:setup-put-cat": "deno test --env-file=.test.env -A --no-check test-setup-put-cat.ts",
|
||||
"test:mirror": "deno test --env-file=.test.env -A --no-check test-mirror.ts",
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Deno port of test-daemon-linux.sh
|
||||
*
|
||||
* Tests daemon-related ignore rules behaviour.
|
||||
*
|
||||
* Tests that are runnable without a long-running daemon process are exercised
|
||||
* here using the 'mirror' command, which calls the same 'isTargetFile' handler
|
||||
* stack that the daemon uses.
|
||||
*
|
||||
* Covered cases:
|
||||
* 1. .livesync/ignore with *.tmp pattern → ignored file is not synced to database
|
||||
* 2. .livesync/ignore missing → no error, and normal synchronisation continues
|
||||
* 3. import: .gitignore directive → patterns from .gitignore are merged
|
||||
*
|
||||
* Run:
|
||||
* deno test -A test-daemon.ts
|
||||
*/
|
||||
|
||||
import { join } from "@std/path";
|
||||
import { assertEquals } from "@std/assert";
|
||||
import { TempDir } from "./helpers/temp.ts";
|
||||
import { runCliOrFail, runCli, assertContains, assertNotContains } from "./helpers/cli.ts";
|
||||
import { initSettingsFile, markSettingsConfigured } from "./helpers/settings.ts";
|
||||
|
||||
Deno.test("daemon: ignore rules behaviour", async (t) => {
|
||||
// -------------------------------------------------------------------------
|
||||
// Case 1: .livesync/ignore with *.tmp → ignored file not synced to database
|
||||
// -------------------------------------------------------------------------
|
||||
await t.step("case 1: .livesync/ignore *.tmp prevents sync", async () => {
|
||||
await using workDir = await TempDir.create("livesync-cli-daemon-c1");
|
||||
const settingsFile = workDir.join("data.json");
|
||||
const vaultDir = workDir.join("vault");
|
||||
|
||||
await Deno.mkdir(join(vaultDir, ".livesync"), { recursive: true });
|
||||
await Deno.mkdir(join(vaultDir, "notes"), { recursive: true });
|
||||
|
||||
await initSettingsFile(settingsFile);
|
||||
await markSettingsConfigured(settingsFile);
|
||||
|
||||
await Deno.writeTextFile(join(vaultDir, ".livesync", "ignore"), "*.tmp\n");
|
||||
await Deno.writeTextFile(join(vaultDir, "notes", "normal.md"), "normal content\n");
|
||||
await Deno.writeTextFile(join(vaultDir, "notes", "scratch.tmp"), "tmp content\n");
|
||||
|
||||
console.log("[INFO] Running mirror for Case 1...");
|
||||
await runCliOrFail(vaultDir, "--settings", settingsFile, "mirror");
|
||||
|
||||
// The normal file should be in the database.
|
||||
const resultNormal = workDir.join("case1-normal.txt");
|
||||
await runCliOrFail(vaultDir, "--settings", settingsFile, "pull", "notes/normal.md", resultNormal);
|
||||
const normalContent = await Deno.readTextFile(resultNormal);
|
||||
assertEquals(normalContent, "normal content\n", "normal.md content mismatch after mirror");
|
||||
|
||||
// The .tmp file should NOT be in the database.
|
||||
const dbList = await runCliOrFail(vaultDir, "--settings", settingsFile, "ls");
|
||||
assertNotContains(dbList, "scratch.tmp", "scratch.tmp (ignored) was unexpectedly synced to database");
|
||||
assertContains(dbList, "normal.md", "normal.md was not found in database after mirror");
|
||||
console.log("[PASS] Case 1 verified successfully");
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Case 2: .livesync/ignore absent → no error, and normal synchronisation continues
|
||||
// -------------------------------------------------------------------------
|
||||
await t.step("case 2: .livesync/ignore absent does not cause failure", async () => {
|
||||
await using workDir = await TempDir.create("livesync-cli-daemon-c2");
|
||||
const settingsFile = workDir.join("data2.json");
|
||||
const vaultDir = workDir.join("vault2");
|
||||
|
||||
await Deno.mkdir(join(vaultDir, "notes"), { recursive: true });
|
||||
|
||||
await initSettingsFile(settingsFile);
|
||||
await markSettingsConfigured(settingsFile);
|
||||
|
||||
// No .livesync directory at all.
|
||||
await Deno.writeTextFile(join(vaultDir, "notes", "hello.md"), "hello\n");
|
||||
|
||||
console.log("[INFO] Running mirror for Case 2...");
|
||||
const result = await runCli(vaultDir, "--settings", settingsFile, "mirror");
|
||||
assertEquals(result.code, 0, "mirror exited non-zero when .livesync/ignore is absent");
|
||||
|
||||
// The normal file should have been synced.
|
||||
const resultHello = workDir.join("case2-hello.txt");
|
||||
await runCliOrFail(vaultDir, "--settings", settingsFile, "pull", "notes/hello.md", resultHello);
|
||||
const helloContent = await Deno.readTextFile(resultHello);
|
||||
assertEquals(helloContent, "hello\n", "file content mismatch when .livesync/ignore is absent");
|
||||
console.log("[PASS] Case 2 verified successfully");
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Case 3: import: .gitignore merges patterns
|
||||
// -------------------------------------------------------------------------
|
||||
await t.step("case 3: import: .gitignore directive merges patterns", async () => {
|
||||
await using workDir = await TempDir.create("livesync-cli-daemon-c3");
|
||||
const settingsFile = workDir.join("data3.json");
|
||||
const vaultDir = workDir.join("vault3");
|
||||
|
||||
await Deno.mkdir(join(vaultDir, ".livesync"), { recursive: true });
|
||||
await Deno.mkdir(join(vaultDir, "notes"), { recursive: true });
|
||||
|
||||
await initSettingsFile(settingsFile);
|
||||
await markSettingsConfigured(settingsFile);
|
||||
|
||||
await Deno.writeTextFile(join(vaultDir, ".livesync", "ignore"), "import: .gitignore\n");
|
||||
await Deno.writeTextFile(join(vaultDir, ".gitignore"), "# gitignore comment\n*.log\nbuild/\n");
|
||||
|
||||
await Deno.writeTextFile(join(vaultDir, "notes", "regular.md"), "regular note\n");
|
||||
await Deno.writeTextFile(join(vaultDir, "notes", "debug.log"), "log content\n");
|
||||
|
||||
console.log("[INFO] Running mirror for Case 3...");
|
||||
await runCliOrFail(vaultDir, "--settings", settingsFile, "mirror");
|
||||
|
||||
const dbList = await runCliOrFail(vaultDir, "--settings", settingsFile, "ls");
|
||||
assertNotContains(dbList, "debug.log", "debug.log (ignored via .gitignore import) was unexpectedly synced to database");
|
||||
assertContains(dbList, "regular.md", "regular.md was not synced normally alongside .gitignore import rules");
|
||||
console.log("[PASS] Case 3 verified successfully");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Deno port of test-decoupled-vault-linux.sh
|
||||
*
|
||||
* Tests push, pull, and mirror command behaviour when the vault directory is
|
||||
* decoupled (separated) from the database directory.
|
||||
*
|
||||
* Run:
|
||||
* deno test -A test-decoupled-vault.ts
|
||||
*/
|
||||
|
||||
import { join } from "@std/path";
|
||||
import { assertEquals } from "@std/assert";
|
||||
import { TempDir } from "./helpers/temp.ts";
|
||||
import { runCliOrFail } from "./helpers/cli.ts";
|
||||
import { applyCouchdbSettings, initSettingsFile, markSettingsConfigured } from "./helpers/settings.ts";
|
||||
import { startCouchdb, stopCouchdb } from "./helpers/docker.ts";
|
||||
|
||||
const REMOTE_PATH = Deno.env.get("REMOTE_PATH") ?? "test/push-pull-decoupled.txt";
|
||||
|
||||
Deno.test("decoupled database and vault", async () => {
|
||||
await using workDir = await TempDir.create("livesync-cli-decoupled");
|
||||
|
||||
const settingsFile = workDir.join("data.json");
|
||||
const vaultDir = workDir.join("vault");
|
||||
const dbDir = workDir.join("db");
|
||||
|
||||
await Deno.mkdir(join(vaultDir, "test"), { recursive: true });
|
||||
await Deno.mkdir(dbDir, { recursive: true });
|
||||
|
||||
const uri = Deno.env.get("COUCHDB_URI") ?? "http://127.0.0.1:5989/";
|
||||
const user = Deno.env.get("COUCHDB_USER") ?? "admin";
|
||||
const password = Deno.env.get("COUCHDB_PASSWORD") ?? "testpassword";
|
||||
const dbname = Deno.env.get("COUCHDB_DBNAME") ?? `decoupled-${Date.now()}`;
|
||||
|
||||
const shouldStartDocker = Deno.env.get("LIVESYNC_START_DOCKER") !== "0";
|
||||
const keepDocker = Deno.env.get("LIVESYNC_DEBUG_KEEP_DOCKER") === "1";
|
||||
|
||||
if (shouldStartDocker) {
|
||||
await startCouchdb(uri, user, password, dbname);
|
||||
}
|
||||
|
||||
try {
|
||||
await initSettingsFile(settingsFile);
|
||||
|
||||
if (uri && user && password && dbname) {
|
||||
console.log("[INFO] applying CouchDB environment variables to settings");
|
||||
await applyCouchdbSettings(settingsFile, uri, user, password, dbname);
|
||||
} else {
|
||||
console.warn(
|
||||
"[WARN] CouchDB environment variables are not fully set. Push and pull operations may fail."
|
||||
);
|
||||
await markSettingsConfigured(settingsFile);
|
||||
}
|
||||
|
||||
const srcFile = workDir.join("push-source.txt");
|
||||
const pulledFile = workDir.join("pull-result.txt");
|
||||
const content = `push-pull-decoupled-test ${new Date().toISOString()}\n`;
|
||||
await Deno.writeTextFile(srcFile, content);
|
||||
|
||||
// 1. Test push command with decoupled vault directory
|
||||
console.log(`[INFO] push with decoupled vault -> ${REMOTE_PATH}`);
|
||||
await runCliOrFail(
|
||||
dbDir,
|
||||
"--vault",
|
||||
vaultDir,
|
||||
"--settings",
|
||||
settingsFile,
|
||||
"push",
|
||||
srcFile,
|
||||
REMOTE_PATH
|
||||
);
|
||||
|
||||
// 2. Test pull command with decoupled vault directory
|
||||
console.log(`[INFO] pull with decoupled vault <- ${REMOTE_PATH}`);
|
||||
await runCliOrFail(
|
||||
dbDir,
|
||||
"--vault",
|
||||
vaultDir,
|
||||
"--settings",
|
||||
settingsFile,
|
||||
"pull",
|
||||
REMOTE_PATH,
|
||||
pulledFile
|
||||
);
|
||||
|
||||
const pulled = await Deno.readTextFile(pulledFile);
|
||||
assertEquals(pulled, content, "push/pull roundtrip with decoupled vault content mismatch");
|
||||
console.log("[PASS] push/pull roundtrip with decoupled vault matched");
|
||||
|
||||
// 3. Clean up pulled file and vault test directory to verify mirror
|
||||
await Deno.remove(pulledFile).catch(() => {});
|
||||
await Deno.remove(join(vaultDir, "test"), { recursive: true }).catch(() => {});
|
||||
|
||||
// 4. Test mirror command with decoupled vault directory
|
||||
console.log("[INFO] mirror with decoupled vault");
|
||||
await runCliOrFail(
|
||||
dbDir,
|
||||
"--vault",
|
||||
vaultDir,
|
||||
"--settings",
|
||||
settingsFile,
|
||||
"mirror"
|
||||
);
|
||||
|
||||
const restoredFile = join(vaultDir, REMOTE_PATH);
|
||||
const restored = await Deno.readTextFile(restoredFile);
|
||||
assertEquals(restored, content, "mirror with decoupled vault content mismatch");
|
||||
console.log("[PASS] mirror with decoupled vault matched");
|
||||
} finally {
|
||||
if (shouldStartDocker && !keepDocker) {
|
||||
await stopCouchdb().catch(() => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Deno port of test-remote-commands-linux.sh
|
||||
*
|
||||
* Tests remote management commands: remote-status, lock-remote, unlock-remote,
|
||||
* and mark-resolved.
|
||||
*
|
||||
* Scenario:
|
||||
* 1. Start CouchDB, create a test database, and perform an initial sync.
|
||||
* 2. Run remote-status and assert that the output contains the database name in JSON format.
|
||||
* 3. Run lock-remote and verify that the remote database is locked.
|
||||
* 4. Lock the remote database milestone manually, verify status, and run unlock-remote.
|
||||
* Assert that the output of unlock-remote contains the unlocked verification status.
|
||||
* 5. Lock the remote database milestone manually, run mark-resolved, and verify that the
|
||||
* current device is accepted.
|
||||
*
|
||||
* Run:
|
||||
* deno test -A test-remote-commands.ts
|
||||
*/
|
||||
|
||||
import { join } from "@std/path";
|
||||
import { TempDir } from "./helpers/temp.ts";
|
||||
import { runCli, assertContains } from "./helpers/cli.ts";
|
||||
import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts";
|
||||
import { startCouchdb, stopCouchdb, updateCouchdbDoc } from "./helpers/docker.ts";
|
||||
|
||||
async function runCliCombinedOrFail(...args: string[]): Promise<string> {
|
||||
const res = await runCli(...args);
|
||||
if (res.code !== 0) {
|
||||
throw new Error(`CLI exited with code ${res.code}\nstdout: ${res.stdout}\nstderr: ${res.stderr}`);
|
||||
}
|
||||
return res.combined;
|
||||
}
|
||||
|
||||
Deno.test("remote management commands", async () => {
|
||||
await using workDir = await TempDir.create("livesync-cli-remote-cmds");
|
||||
|
||||
const settingsFile = workDir.join("settings.json");
|
||||
const vaultDir = workDir.join("vault");
|
||||
await Deno.mkdir(vaultDir, { recursive: true });
|
||||
|
||||
const uri = Deno.env.get("COUCHDB_URI") ?? "http://127.0.0.1:5989/";
|
||||
const user = Deno.env.get("COUCHDB_USER") ?? "admin";
|
||||
const password = Deno.env.get("COUCHDB_PASSWORD") ?? "testpassword";
|
||||
const dbSuffix = `${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
||||
const dbname = Deno.env.get("COUCHDB_DBNAME") ?? `remotes-${dbSuffix}`;
|
||||
|
||||
const shouldStartDocker = Deno.env.get("LIVESYNC_START_DOCKER") !== "0";
|
||||
const keepDocker = Deno.env.get("LIVESYNC_DEBUG_KEEP_DOCKER") === "1";
|
||||
|
||||
if (shouldStartDocker) {
|
||||
await startCouchdb(uri, user, password, dbname);
|
||||
}
|
||||
|
||||
try {
|
||||
await initSettingsFile(settingsFile);
|
||||
await applyCouchdbSettings(settingsFile, uri, user, password, dbname, true);
|
||||
|
||||
console.log("[INFO] Performing initial sync to create milestone document...");
|
||||
await runCliCombinedOrFail(vaultDir, "--settings", settingsFile, "sync");
|
||||
|
||||
// 1. remote-status outputs valid JSON with CouchDB details
|
||||
console.log("[CASE] remote-status outputs valid JSON with CouchDB details");
|
||||
const statusOutput = await runCliCombinedOrFail(vaultDir, "--settings", settingsFile, "remote-status");
|
||||
assertContains(
|
||||
statusOutput,
|
||||
`"db_name": "${dbname}"`,
|
||||
"remote-status should return JSON containing db_name"
|
||||
);
|
||||
console.log("[PASS] remote-status verified");
|
||||
|
||||
// 2. lock-remote locks and verifies state
|
||||
console.log("[CASE] lock-remote locks and verifies state");
|
||||
const lockOutput = await runCliCombinedOrFail(vaultDir, "--settings", settingsFile, "lock-remote");
|
||||
assertContains(
|
||||
lockOutput,
|
||||
"[Verification] Remote Database: LOCKED",
|
||||
"lock-remote output should show that the remote database is locked"
|
||||
);
|
||||
console.log("[PASS] lock-remote verified");
|
||||
|
||||
// 3. unlock-remote unlocks and verifies state
|
||||
console.log("[CASE] unlock-remote unlocks and verifies state");
|
||||
// Manually lock milestone
|
||||
console.log("[INFO] Manually locking milestone...");
|
||||
await updateCouchdbDoc(uri, user, password, `${dbname}/_local/obsydian_livesync_milestone`, (doc) => {
|
||||
doc.locked = true;
|
||||
doc.accepted_nodes = [];
|
||||
return doc;
|
||||
});
|
||||
|
||||
// Run unlock-remote and verify output contains verification message
|
||||
const unlockOutput = await runCliCombinedOrFail(vaultDir, "--settings", settingsFile, "unlock-remote");
|
||||
assertContains(
|
||||
unlockOutput,
|
||||
"[Verification] Remote Database: UNLOCKED",
|
||||
"unlock-remote output should contain verification status"
|
||||
);
|
||||
console.log("[PASS] unlock-remote verified");
|
||||
|
||||
// 4. mark-resolved resolves and verifies state
|
||||
console.log("[CASE] mark-resolved resolves and verifies state");
|
||||
// Manually lock milestone
|
||||
console.log("[INFO] Manually locking milestone...");
|
||||
await updateCouchdbDoc(uri, user, password, `${dbname}/_local/obsydian_livesync_milestone`, (doc) => {
|
||||
doc.locked = true;
|
||||
doc.accepted_nodes = [];
|
||||
return doc;
|
||||
});
|
||||
|
||||
// Run mark-resolved and verify output contains verification messages
|
||||
const resolvedOutput = await runCliCombinedOrFail(vaultDir, "--settings", settingsFile, "mark-resolved");
|
||||
assertContains(
|
||||
resolvedOutput,
|
||||
"[Verification] Remote Database: LOCKED",
|
||||
"mark-resolved output should show that the remote database remains locked"
|
||||
);
|
||||
assertContains(
|
||||
resolvedOutput,
|
||||
"ACCEPTED",
|
||||
"mark-resolved output should show that the current device node is accepted"
|
||||
);
|
||||
console.log("[PASS] mark-resolved verified");
|
||||
|
||||
console.log("[ALL PASS] All remote CLI commands verified successfully");
|
||||
} finally {
|
||||
if (shouldStartDocker && !keepDocker) {
|
||||
await stopCouchdb().catch(() => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -39,6 +39,9 @@ src/apps/cli/testdeno/
|
||||
test-mirror.ts
|
||||
test-sync-two-local-databases.ts
|
||||
test-sync-locked-remote.ts
|
||||
test-daemon.ts
|
||||
test-decoupled-vault.ts
|
||||
test-remote-commands.ts
|
||||
```
|
||||
|
||||
---
|
||||
@@ -54,6 +57,9 @@ Main tasks:
|
||||
|
||||
- `deno task test`
|
||||
- `deno task test:local`
|
||||
- `deno task test:daemon`
|
||||
- `deno task test:decoupled-vault`
|
||||
- `deno task test:remote-commands`
|
||||
- `deno task test:push-pull`
|
||||
- `deno task test:setup-put-cat`
|
||||
- `deno task test:mirror`
|
||||
@@ -183,6 +189,19 @@ Both CouchDB and P2P relay flows are bash-independent.
|
||||
- `MINIO-enc0`
|
||||
- `MINIO-enc1`
|
||||
|
||||
### `test-daemon.ts`
|
||||
|
||||
- Verifies daemon-related ignore rules behaviour.
|
||||
- Exercises scenarios with `.livesync/ignore` wildcard rules, missing ignore rules, and imported `.gitignore` rules.
|
||||
|
||||
### `test-decoupled-vault.ts`
|
||||
|
||||
- Verifies push, pull, and mirror command behaviour when the vault directory is decoupled from the database directory.
|
||||
|
||||
### `test-remote-commands.ts`
|
||||
|
||||
- Verifies remote database management commands: `remote-status`, `lock-remote`, `unlock-remote`, and `mark-resolved`.
|
||||
|
||||
---
|
||||
|
||||
## Running tests (PowerShell)
|
||||
@@ -198,11 +217,14 @@ deno task test:local
|
||||
# Individual tests
|
||||
deno task test:setup-put-cat
|
||||
deno task test:mirror
|
||||
deno task test:daemon
|
||||
deno task test:push-pull
|
||||
deno task test:sync-locked-remote
|
||||
|
||||
# CouchDB-based tests
|
||||
deno task test:sync-two-local
|
||||
deno task test:decoupled-vault
|
||||
deno task test:remote-commands
|
||||
deno task test:e2e-couchdb
|
||||
|
||||
# P2P-based tests
|
||||
|
||||
Reference in New Issue
Block a user