test: verify the packaged Commonlib boundary

This commit is contained in:
vorotamoroz
2026-07-17 17:50:56 +00:00
parent f80137e364
commit 7b7801b029
12 changed files with 176 additions and 26 deletions
-8
View File
@@ -25,8 +25,6 @@ on:
type: choice
options:
- test:ci
- test:p2p
- test:all
- test:local
- test:e2e-matrix
default: test:ci
@@ -60,12 +58,6 @@ jobs:
test:ci)
TASK_MATRIX='["test:setup-put-cat","test:mirror","test:daemon","test:push-pull","test:decoupled-vault","test:sync-two-local","test:sync-locked-remote","test:remote-commands","test:e2e-matrix:couchdb-enc0","test:e2e-matrix:couchdb-enc1","test:e2e-matrix:minio-enc0","test:e2e-matrix:minio-enc1"]'
;;
test:p2p)
TASK_MATRIX='["test:p2p-host","test:p2p-peers","test:p2p-sync","test:p2p-three-nodes","test:p2p-upload-download"]'
;;
test:all)
TASK_MATRIX='["test:setup-put-cat","test:mirror","test:daemon","test:push-pull","test:decoupled-vault","test:sync-two-local","test:sync-locked-remote","test:remote-commands","test:p2p-host","test:p2p-peers","test:p2p-sync","test:p2p-three-nodes","test:p2p-upload-download","test:e2e-matrix:couchdb-enc0","test:e2e-matrix:couchdb-enc1","test:e2e-matrix:minio-enc0","test:e2e-matrix:minio-enc1"]'
;;
test:local)
TASK_MATRIX='["test:setup-put-cat","test:mirror","test:daemon"]'
;;
+4 -4
View File
@@ -23,7 +23,7 @@
"@smithy/types": "^4.14.3",
"@smithy/util-retry": "^4.4.5",
"@trystero-p2p/nostr": "^0.24.0",
"@vrtmrz/livesync-commonlib": "0.1.0-rc.0",
"@vrtmrz/livesync-commonlib": "0.1.0-rc.1",
"diff-match-patch": "^1.0.5",
"fflate": "^0.8.2",
"idb": "^8.0.3",
@@ -4978,9 +4978,9 @@
}
},
"node_modules/@vrtmrz/livesync-commonlib": {
"version": "0.1.0-rc.0",
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.0-rc.0.tgz",
"integrity": "sha512-Aa+xC7bG78M7H8leIMmZdTRzA0Xh+ZgmiZJv0pXjy5OdZTPDu1H9M0eRcs5xL2pNBiOLKs0CKiEBql7JgunnQg==",
"version": "0.1.0-rc.1",
"resolved": "https://registry.npmjs.org/@vrtmrz/livesync-commonlib/-/livesync-commonlib-0.1.0-rc.1.tgz",
"integrity": "sha512-OTZ2l1GyWBe7NWMRrWInZVfiGKTsh9zvjhpWj43Tsox/2JFNaa5LfZBunGV5UcjvGpD5cXGDAw94cQ9TdJBAdg==",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.808.0",
+4 -1
View File
@@ -29,6 +29,9 @@
"test:contract:context:webapp": "vitest run --config vitest.config.unit.ts src/apps/browser/createLiveSyncBrowserServiceHub.unit.spec.ts",
"test:contract:context:cli": "npm run build --workspace self-hosted-livesync-cli && deno task --cwd src/apps/cli/testdeno test:setup-put-cat",
"test:contract:context:obsidian": "npm run build && npm run test:e2e:obsidian:smoke",
"test:e2e:cli": "npm run test:e2e:ci --workspace self-hosted-livesync-cli",
"test:e2e:cli:p2p": "npm run test:e2e:p2p --workspace self-hosted-livesync-cli",
"test:e2e:cli:all": "npm run test:e2e:all --workspace self-hosted-livesync-cli",
"test:integration": "npx dotenv-cli -e .env -e .test.env -- vitest run --config vitest.config.integration.ts",
"test:unit:coverage": "vitest run --config vitest.config.unit.ts --coverage",
"test:install-playwright": "npx playwright install chromium",
@@ -155,7 +158,7 @@
"@smithy/types": "^4.14.3",
"@smithy/util-retry": "^4.4.5",
"@trystero-p2p/nostr": "^0.24.0",
"@vrtmrz/livesync-commonlib": "0.1.0-rc.0",
"@vrtmrz/livesync-commonlib": "0.1.0-rc.1",
"diff-match-patch": "^1.0.5",
"fflate": "^0.8.2",
"idb": "^8.0.3",
+5 -2
View File
@@ -11,7 +11,10 @@ import {
type EntryDoc,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
import { activateRemoteConfiguration, createRemoteConfigurationId } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
import {
activateRemoteConfiguration,
createRemoteConfigurationId,
} from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig";
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
import type { CLICommandContext, CLIOptions } from "./types";
import { toArrayBuffer, toDatabaseRelativePath } from "./utils";
@@ -22,7 +25,7 @@ import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFu
import { fsPromises as fs, path } from "@vrtmrz/livesync-commonlib/node";
import type { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator";
import type { LiveSyncJournalReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/journal/LiveSyncJournalReplicator";
import { writeStderrLine, writeStdoutLine } from "../cliOutput";
import { writeStderrLine, writeStdoutLine } from "@/apps/cli/cliOutput";
function redactConnectionString(uri: string): string {
return uri.replace(/\/\/([^@/]+)@/u, "//***@");
+4 -7
View File
@@ -19,22 +19,19 @@
"test:e2e:push-pull": "bash test/test-push-pull-linux.sh",
"test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh",
"test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh",
"test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh",
"test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh",
"test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh",
"test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh",
"pretest:e2e:ci": "npm run build",
"test:e2e:ci": "deno task --cwd testdeno test:ci",
"test:e2e:p2p": "deno task --cwd testdeno test:p2p:compose",
"test:e2e:mirror": "bash test/test-mirror-linux.sh",
"test:e2e:remote-commands": "bash test/test-remote-commands-linux.sh",
"pretest:e2e:all": "npm run build",
"test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:remote-commands",
"test:e2e:all": "deno task --cwd testdeno test:ci && deno task --cwd testdeno test:p2p:compose",
"pretest:e2e:docker:all": "npm run build:docker",
"test:e2e:docker:push-pull": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-push-pull-linux.sh",
"test:e2e:docker:setup-put-cat": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-setup-put-cat-linux.sh",
"test:e2e:docker:mirror": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-mirror-linux.sh",
"test:e2e:docker:remote-commands": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-remote-commands-linux.sh",
"test:e2e:docker:sync-two-local": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-sync-two-local-databases-linux.sh",
"test:e2e:docker:p2p": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-three-nodes-conflict-linux.sh",
"test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh",
"test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror && npm run test:e2e:docker:remote-commands"
},
"dependencies": {
@@ -107,6 +107,7 @@ Deno.test("feature: behaviour", async () => {
- Re-run sync operations where the protocol is eventually consistent.
- For network-sensitive commands, use `LIVESYNC_CLI_RETRY` during debugging.
- Keep Docker container reuse disabled by default unless debugging.
- Use `npm run test:e2e:cli:p2p` for canonical P2P validation. It runs the Deno scenario in Compose because host networking and WebRTC candidate selection are not reproducible across environments. Individual `deno task test:p2p-*` tasks remain available when explicitly invoked for cross-platform diagnostics, but are not selected by the default suite or CI.
## Environment variables
+3 -1
View File
@@ -1,6 +1,8 @@
{
"tasks": {
"test": "deno test --env-file=.test.env -A --no-check test-*.ts",
"test": "deno task test:ci",
"test:ci": "deno run -A --no-check run-ci-suite.ts",
"test:p2p:compose": "deno run -A --no-check run-compose-p2p.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",
+32
View File
@@ -0,0 +1,32 @@
const TASKS = [
"test:setup-put-cat",
"test:mirror",
"test:daemon",
"test:push-pull",
"test:decoupled-vault",
"test:sync-two-local",
"test:sync-locked-remote",
"test:remote-commands",
"test:e2e-matrix:couchdb-enc0",
"test:e2e-matrix:couchdb-enc1",
"test:e2e-matrix:minio-enc0",
"test:e2e-matrix:minio-enc1",
] as const;
for (const [index, task] of TASKS.entries()) {
console.log(`\n[CLI E2E ${index + 1}/${TASKS.length}] ${task}`);
const child = new Deno.Command(Deno.execPath(), {
args: ["task", task],
cwd: import.meta.dirname,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
}).spawn();
const status = await child.status;
if (!status.success) {
console.error(`[CLI E2E] ${task} failed with exit code ${status.code}.`);
Deno.exit(status.code);
}
}
console.log(`\n[CLI E2E] CI suite passed (${TASKS.length} tasks).`);
+48
View File
@@ -0,0 +1,48 @@
const repositoryRoot = await Deno.realPath(new URL("../../../../", import.meta.url));
const composeArgs = ["compose", "-f", "test/bench-network/compose.yml"];
const p2pEnvironment = {
CLI_E2E_TASK: Deno.env.get("CLI_E2E_TASK") ?? "test:p2p-sync",
RELAY: Deno.env.get("RELAY") ?? "ws://nostr-relay:7777/",
PEERS_TIMEOUT: Deno.env.get("PEERS_TIMEOUT") ?? "20",
SYNC_TIMEOUT: Deno.env.get("SYNC_TIMEOUT") ?? "60",
LIVESYNC_USE_COTURN: Deno.env.get("LIVESYNC_USE_COTURN") ?? "0",
TURN_SERVERS: Deno.env.get("TURN_SERVERS") ?? "none",
LIVESYNC_P2P_PEERS_RETRY: Deno.env.get("LIVESYNC_P2P_PEERS_RETRY") ?? "1",
LIVESYNC_P2P_RELAY_READY_TIMEOUT_MS: Deno.env.get("LIVESYNC_P2P_RELAY_READY_TIMEOUT_MS") ?? "60000",
BENCH_LIVESYNC_TEST_TEE: Deno.env.get("BENCH_LIVESYNC_TEST_TEE") ?? "0",
};
async function runDocker(args: string[], env?: Record<string, string>): Promise<Deno.CommandStatus> {
return await new Deno.Command("docker", {
args,
cwd: repositoryRoot,
env,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
}).spawn().status;
}
let testStatus: Deno.CommandStatus | undefined;
try {
testStatus = await runDocker(
[...composeArgs, "run", "--build", "--rm", "bench-runner", "run-livesync-cli-e2e"],
p2pEnvironment
);
} finally {
const cleanupStatus = await runDocker([...composeArgs, "down", "-v", "--remove-orphans"]);
if (!cleanupStatus.success) {
console.error(`[CLI E2E] Compose cleanup failed with exit code ${cleanupStatus.code}.`);
if (testStatus?.success) {
Deno.exit(cleanupStatus.code);
}
}
}
if (!testStatus?.success) {
const code = testStatus?.code ?? 1;
console.error(`[CLI E2E] Compose P2P suite failed with exit code ${code}.`);
Deno.exit(code);
}
console.log("\n[CLI E2E] Compose P2P suite passed.");
+22 -3
View File
@@ -1,13 +1,13 @@
# CLI Deno Test Development Notes
This document provides an overview of the Deno-based compatibility tests under `src/apps/cli/testdeno/`.
The existing bash tests under `src/apps/cli/test/` are preserved, while a Windows-friendly suite is maintained in parallel.
The Deno suite is the canonical CLI E2E entry point. P2P scenarios run through the repository Compose entry point so that networking, signalling, and the runner environment are reproducible. Existing Bash tests under `src/apps/cli/test/` remain as legacy implementation references, but are not exposed as supported P2P entry points.
---
## Goals
- Keep existing bash tests intact.
- Keep the existing Bash tests as migration references while using Deno and Compose for supported execution.
- Provide direct execution from Windows PowerShell.
- Establish a TypeScript (Deno) foundation for core end-to-end and integration scenarios.
@@ -18,6 +18,8 @@ The existing bash tests under `src/apps/cli/test/` are preserved, while a Window
```
src/apps/cli/testdeno/
deno.json
run-ci-suite.ts
run-compose-p2p.ts
CONTRIBUTING_TESTS.md
helpers/
backgroundCli.ts
@@ -56,6 +58,8 @@ src/apps/cli/testdeno/
Main tasks:
- `deno task test`
- `deno task test:ci`
- `deno task test:p2p:compose`
- `deno task test:local`
- `deno task test:daemon`
- `deno task test:decoupled-vault`
@@ -73,6 +77,8 @@ Main tasks:
- `deno task test:e2e-couchdb`
- `deno task test:e2e-matrix`
`deno task test` is an alias for the non-P2P `test:ci` suite. The individual P2P tasks are explicit host-direct entry points for cross-platform diagnostics; they are never selected by the default suite or CI. Use `test:p2p:compose` for canonical P2P verification.
### `helpers/cli.ts`
- CLI execution wrappers.
@@ -206,11 +212,23 @@ Both CouchDB and P2P relay flows are bash-independent.
## Running tests (PowerShell)
From the repository root, use the canonical package scripts. `test:e2e:cli` runs the same non-P2P task set selected by the default CLI CI workflow. P2P validation runs in Compose so peer discovery does not depend on host loopback, firewall, or WebRTC candidate behaviour.
```powershell
npm run test:e2e:cli
npm run test:e2e:cli:p2p
npm run test:e2e:cli:all
```
From `src/apps/cli/testdeno`:
```powershell
cd src/apps/cli/testdeno
# Canonical suites
deno task test:ci
deno task test:p2p:compose
# Local-only set
deno task test:local
@@ -227,7 +245,8 @@ deno task test:decoupled-vault
deno task test:remote-commands
deno task test:e2e-couchdb
# P2P-based tests
# Explicit host-direct P2P diagnostics for cross-platform investigations.
# These are not part of the default suite or release evidence.
deno task test:p2p-host
deno task test:p2p-peers
deno task test:p2p-sync
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { fileURLToPath, path } from "@vrtmrz/livesync-commonlib/node";
import * as ts from "typescript";
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
const dependencyFacadePath = path.resolve(repositoryRoot, "src/deps.ts");
const obsidianMockPath = path.resolve(repositoryRoot, "test/harness/obsidian-mock.ts");
describe("Webapp Obsidian mock exports", () => {
it("provides every value re-exported from Obsidian", () => {
const program = ts.createProgram([dependencyFacadePath, obsidianMockPath], {
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
skipLibCheck: true,
target: ts.ScriptTarget.ESNext,
});
const dependencyFacade = program.getSourceFile(dependencyFacadePath);
const obsidianMock = program.getSourceFile(obsidianMockPath);
expect(dependencyFacade).toBeDefined();
expect(obsidianMock).toBeDefined();
const requiredExports = new Set<string>();
for (const statement of dependencyFacade!.statements) {
if (
ts.isExportDeclaration(statement) &&
statement.moduleSpecifier &&
ts.isStringLiteral(statement.moduleSpecifier) &&
statement.moduleSpecifier.text === "obsidian" &&
statement.exportClause &&
ts.isNamedExports(statement.exportClause) &&
!statement.isTypeOnly
) {
for (const element of statement.exportClause.elements) {
if (!element.isTypeOnly) {
requiredExports.add((element.propertyName ?? element.name).text);
}
}
}
}
const checker = program.getTypeChecker();
const mockSymbol = checker.getSymbolAtLocation(obsidianMock!);
expect(mockSymbol).toBeDefined();
const availableExports = new Set(checker.getExportsOfModule(mockSymbol!).map((symbol) => symbol.name));
const missingExports = [...requiredExports].filter((name) => !availableExports.has(name)).sort();
expect(missingExports).toEqual([]);
});
});
+4
View File
@@ -980,6 +980,10 @@ export function parseYaml(s: string) {
export function getLanguage() {
return "en";
}
/** The mock does not implement APIs gated by an Obsidian application version. */
export function requireApiVersion(_version: string): boolean {
return false;
}
export function setIcon(el: HTMLElement, icon: string) {}
export function arrayBufferToBase64(buffer: ArrayBuffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buffer)));