mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-18 15:22:31 +00:00
- Add default test environment
- Fixed to use environment by APIs - Make test parallel
This commit is contained in:
@@ -17,9 +17,48 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
prepare:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
task_matrix: ${{ steps.select.outputs.task_matrix }}
|
||||||
|
steps:
|
||||||
|
- name: Select task matrix
|
||||||
|
id: select
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
SELECTED_TASK="${{ github.event_name == 'workflow_dispatch' && inputs.test_task || 'test' }}"
|
||||||
|
echo "[INFO] Selected task set: $SELECTED_TASK"
|
||||||
|
|
||||||
|
case "$SELECTED_TASK" in
|
||||||
|
test)
|
||||||
|
TASK_MATRIX='["test:setup-put-cat","test:mirror","test:push-pull","test:sync-two-local","test:sync-locked-remote","test:p2p-host","test:p2p-peers","test:p2p-sync","test:p2p-three-nodes","test:p2p-upload-download","test:e2e-couchdb","test:e2e-matrix"]'
|
||||||
|
;;
|
||||||
|
test:local)
|
||||||
|
TASK_MATRIX='["test:setup-put-cat","test:mirror"]'
|
||||||
|
;;
|
||||||
|
test:e2e-matrix)
|
||||||
|
TASK_MATRIX='["test:e2e-matrix"]'
|
||||||
|
;;
|
||||||
|
test:p2p-sync)
|
||||||
|
TASK_MATRIX='["test:p2p-sync"]'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unknown task set: $SELECTED_TASK" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "task_matrix=$TASK_MATRIX" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
needs: prepare
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
task: ${{ fromJson(needs.prepare.outputs.task_matrix) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -64,7 +103,7 @@ jobs:
|
|||||||
LIVESYNC_DOCKER_MODE: native
|
LIVESYNC_DOCKER_MODE: native
|
||||||
LIVESYNC_CLI_RETRY: 3
|
LIVESYNC_CLI_RETRY: 3
|
||||||
run: |
|
run: |
|
||||||
TASK="${{ github.event_name == 'workflow_dispatch' && inputs.test_task || 'test' }}"
|
TASK="${{ matrix.task }}"
|
||||||
echo "[INFO] Running Deno task: $TASK"
|
echo "[INFO] Running Deno task: $TASK"
|
||||||
deno task "$TASK"
|
deno task "$TASK"
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"test": "deno test -A --no-check test-*.ts",
|
"test": "deno test --env-file=.test.env -A --no-check test-*.ts",
|
||||||
"test:local": "deno test -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:push-pull": "deno test -A --no-check test-push-pull.ts",
|
"test:push-pull": "deno test --env-file=.test.env -A --no-check test-push-pull.ts",
|
||||||
"test:setup-put-cat": "deno test -A --no-check test-setup-put-cat.ts",
|
"test:setup-put-cat": "deno test --env-file=.test.env -A --no-check test-setup-put-cat.ts",
|
||||||
"test:mirror": "deno test -A --no-check test-mirror.ts",
|
"test:mirror": "deno test --env-file=.test.env -A --no-check test-mirror.ts",
|
||||||
"test:sync-two-local": "deno test -A --no-check test-sync-two-local-databases.ts",
|
"test:sync-two-local": "deno test --env-file=.test.env -A --no-check test-sync-two-local-databases.ts",
|
||||||
"test:sync-locked-remote": "deno test -A --no-check test-sync-locked-remote.ts",
|
"test:sync-locked-remote": "deno test --env-file=.test.env -A --no-check test-sync-locked-remote.ts",
|
||||||
"test:p2p-host": "deno test -A --no-check test-p2p-host.ts",
|
"test:p2p-host": "deno test --env-file=.test.env -A --no-check test-p2p-host.ts",
|
||||||
"test:p2p-peers": "deno test -A --no-check test-p2p-peers-local-relay.ts",
|
"test:p2p-peers": "deno test --env-file=.test.env -A --no-check test-p2p-peers-local-relay.ts",
|
||||||
"test:p2p-sync": "deno test -A --no-check test-p2p-sync.ts",
|
"test:p2p-sync": "deno test --env-file=.test.env -A --no-check test-p2p-sync.ts",
|
||||||
"test:p2p-three-nodes": "deno test -A --no-check test-p2p-three-nodes-conflict.ts",
|
"test:p2p-three-nodes": "deno test --env-file=.test.env -A --no-check test-p2p-three-nodes-conflict.ts",
|
||||||
"test:p2p-upload-download": "deno test -A --no-check test-p2p-upload-download-repro.ts",
|
"test:p2p-upload-download": "deno test --env-file=.test.env -A --no-check test-p2p-upload-download-repro.ts",
|
||||||
"test:e2e-couchdb": "deno test -A --no-check test-e2e-two-vaults-couchdb.ts",
|
"test:e2e-couchdb": "deno test --env-file=.test.env -A --no-check test-e2e-two-vaults-couchdb.ts",
|
||||||
"test:e2e-matrix": "deno test -A --no-check test-e2e-two-vaults-matrix.ts"
|
"test:e2e-matrix": "deno test --env-file=.test.env -A --no-check test-e2e-two-vaults-matrix.ts"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"@std/assert": "jsr:@std/assert@^1.0.13",
|
"@std/assert": "jsr:@std/assert@^1.0.13",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { assert } from "@std/assert";
|
import { assert } from "@std/assert";
|
||||||
import { TempDir } from "./helpers/temp.ts";
|
import { TempDir } from "./helpers/temp.ts";
|
||||||
import { loadEnvFile } from "./helpers/env.ts";
|
|
||||||
import {
|
import {
|
||||||
runCli,
|
runCli,
|
||||||
runCliOrFail,
|
runCliOrFail,
|
||||||
@@ -11,31 +10,29 @@ import {
|
|||||||
} from "./helpers/cli.ts";
|
} from "./helpers/cli.ts";
|
||||||
import { applyRemoteSyncSettings, initSettingsFile } from "./helpers/settings.ts";
|
import { applyRemoteSyncSettings, initSettingsFile } from "./helpers/settings.ts";
|
||||||
import { startCouchdb, startMinio, stopCouchdb, stopMinio } from "./helpers/docker.ts";
|
import { startCouchdb, startMinio, stopCouchdb, stopMinio } from "./helpers/docker.ts";
|
||||||
import { join } from "@std/path";
|
|
||||||
|
|
||||||
const TEST_ENV = join(import.meta.dirname!, "..", ".test.env");
|
|
||||||
type RemoteType = "COUCHDB" | "MINIO";
|
type RemoteType = "COUCHDB" | "MINIO";
|
||||||
|
|
||||||
function requireEnv(env: Record<string, string>, key: string): string {
|
function requireEnv(...keys: string[]): string {
|
||||||
const value = env[key]?.trim();
|
for (const key of keys) {
|
||||||
if (!value) throw new Error(`Required env var is missing: ${key}`);
|
const value = Deno.env.get(key)?.trim();
|
||||||
return value;
|
if (value) return value;
|
||||||
|
}
|
||||||
|
throw new Error(`Required env var is missing: ${keys.join(" or ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runScenario(remoteType: RemoteType, encrypt: boolean): Promise<void> {
|
export async function runScenario(remoteType: RemoteType, encrypt: boolean): Promise<void> {
|
||||||
const env = await loadEnvFile(TEST_ENV);
|
|
||||||
const dbSuffix = `${Date.now()}-${Math.floor(Math.random() * 100000)}`;
|
const dbSuffix = `${Date.now()}-${Math.floor(Math.random() * 100000)}`;
|
||||||
|
|
||||||
const couchdbUri = remoteType === "COUCHDB" ? requireEnv(env, "hostname").replace(/\/$/, "") : "";
|
const couchdbUri = remoteType === "COUCHDB" ? requireEnv("COUCHDB_URI", "hostname").replace(/\/$/, "") : "";
|
||||||
const couchdbUser = remoteType === "COUCHDB" ? requireEnv(env, "username") : "";
|
const couchdbUser = remoteType === "COUCHDB" ? requireEnv("COUCHDB_USER", "username") : "";
|
||||||
const couchdbPassword = remoteType === "COUCHDB" ? requireEnv(env, "password") : "";
|
const couchdbPassword = remoteType === "COUCHDB" ? requireEnv("COUCHDB_PASSWORD", "password") : "";
|
||||||
const dbPrefix = remoteType === "COUCHDB" ? requireEnv(env, "dbname") : "";
|
const dbPrefix = remoteType === "COUCHDB" ? requireEnv("COUCHDB_DBNAME", "dbname") : "";
|
||||||
const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : "";
|
const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : "";
|
||||||
|
|
||||||
const minioEndpoint = remoteType === "MINIO" ? requireEnv(env, "minioEndpoint").replace(/\/$/, "") : "";
|
const minioEndpoint = remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : "";
|
||||||
const minioAccessKey = remoteType === "MINIO" ? requireEnv(env, "accessKey") : "";
|
const minioAccessKey = remoteType === "MINIO" ? requireEnv("MINIO_ACCESS_KEY", "accessKey") : "";
|
||||||
const minioSecretKey = remoteType === "MINIO" ? requireEnv(env, "secretKey") : "";
|
const minioSecretKey = remoteType === "MINIO" ? requireEnv("MINIO_SECRET_KEY", "secretKey") : "";
|
||||||
const minioBucketBase = remoteType === "MINIO" ? requireEnv(env, "bucketName") : "";
|
const minioBucketBase = remoteType === "MINIO" ? requireEnv("MINIO_BUCKET_NAME", "bucketName") : "";
|
||||||
const minioBucket = remoteType === "MINIO" ? `${minioBucketBase}-${dbSuffix}` : "";
|
const minioBucket = remoteType === "MINIO" ? `${minioBucketBase}-${dbSuffix}` : "";
|
||||||
|
|
||||||
const passphrase = "e2e-passphrase";
|
const passphrase = "e2e-passphrase";
|
||||||
|
|||||||
@@ -6,30 +6,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, assertStringIncludes } from "@std/assert";
|
import { assert, assertStringIncludes } from "@std/assert";
|
||||||
import { join } from "@std/path";
|
|
||||||
import { loadEnvFile } from "./helpers/env.ts";
|
|
||||||
import { TempDir } from "./helpers/temp.ts";
|
import { TempDir } from "./helpers/temp.ts";
|
||||||
import { runCli } from "./helpers/cli.ts";
|
import { runCli } from "./helpers/cli.ts";
|
||||||
import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts";
|
import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts";
|
||||||
import { createCouchdbDatabase, startCouchdb, stopCouchdb, updateCouchdbDoc } from "./helpers/docker.ts";
|
import { createCouchdbDatabase, startCouchdb, stopCouchdb, updateCouchdbDoc } from "./helpers/docker.ts";
|
||||||
|
|
||||||
const TEST_ENV = join(import.meta.dirname!, "..", ".test.env");
|
|
||||||
const MILESTONE_DOC = "_local/obsydian_livesync_milestone";
|
const MILESTONE_DOC = "_local/obsydian_livesync_milestone";
|
||||||
|
|
||||||
function requireEnv(env: Record<string, string>, key: string): string {
|
function requireEnv(...keys: string[]): string {
|
||||||
const value = env[key]?.trim();
|
for (const key of keys) {
|
||||||
if (!value) {
|
const value = Deno.env.get(key)?.trim();
|
||||||
throw new Error(`Required env var is missing: ${key}`);
|
if (value) return value;
|
||||||
}
|
}
|
||||||
return value;
|
throw new Error(`Required env var is missing: ${keys.join(" or ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
Deno.test("sync: actionable error against locked remote DB", async () => {
|
Deno.test("sync: actionable error against locked remote DB", async () => {
|
||||||
const env = await loadEnvFile(TEST_ENV);
|
const couchdbUri = requireEnv("COUCHDB_URI", "hostname").replace(/\/$/, "");
|
||||||
const couchdbUri = requireEnv(env, "hostname").replace(/\/$/, "");
|
const couchdbUser = requireEnv("COUCHDB_USER", "username");
|
||||||
const couchdbUser = requireEnv(env, "username");
|
const couchdbPassword = requireEnv("COUCHDB_PASSWORD", "password");
|
||||||
const couchdbPassword = requireEnv(env, "password");
|
const dbPrefix = requireEnv("COUCHDB_DBNAME", "dbname");
|
||||||
const dbPrefix = requireEnv(env, "dbname");
|
|
||||||
const dbname = `${dbPrefix}-locked-${Date.now()}-${Math.floor(Math.random() * 100000)}`;
|
const dbname = `${dbPrefix}-locked-${Date.now()}-${Math.floor(Math.random() * 100000)}`;
|
||||||
|
|
||||||
await using workDir = await TempDir.create("livesync-cli-locked-test");
|
await using workDir = await TempDir.create("livesync-cli-locked-test");
|
||||||
|
|||||||
@@ -23,13 +23,11 @@
|
|||||||
* deno test -A test-sync-two-local-databases.ts
|
* deno test -A test-sync-two-local-databases.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { join } from "@std/path";
|
|
||||||
import { assertEquals, assert } from "@std/assert";
|
import { assertEquals, assert } from "@std/assert";
|
||||||
import { TempDir } from "./helpers/temp.ts";
|
import { TempDir } from "./helpers/temp.ts";
|
||||||
import { CLI_DIR, runCliOrFail, jsonFieldIsNa } from "./helpers/cli.ts";
|
import { runCliOrFail, jsonFieldIsNa } from "./helpers/cli.ts";
|
||||||
import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts";
|
import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts";
|
||||||
import { startCouchdb, stopCouchdb } from "./helpers/docker.ts";
|
import { startCouchdb, stopCouchdb } from "./helpers/docker.ts";
|
||||||
import { loadEnvFile } from "./helpers/env.ts";
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Load configuration
|
// Load configuration
|
||||||
@@ -41,20 +39,7 @@ async function resolveConfig(): Promise<{
|
|||||||
password: string;
|
password: string;
|
||||||
baseDbname: string;
|
baseDbname: string;
|
||||||
} | null> {
|
} | null> {
|
||||||
let env: Record<string, string> = {};
|
const env = Deno.env.toObject();
|
||||||
|
|
||||||
// 1. Explicit environment variables take priority
|
|
||||||
if (Deno.env.get("COUCHDB_URI")) {
|
|
||||||
env = Object.fromEntries(Deno.env.toObject());
|
|
||||||
} else {
|
|
||||||
// 2. TEST_ENV_FILE env var
|
|
||||||
const envFile = Deno.env.get("TEST_ENV_FILE") ?? join(CLI_DIR, ".test.env");
|
|
||||||
try {
|
|
||||||
env = await loadEnvFile(envFile);
|
|
||||||
} catch {
|
|
||||||
return null; // no config available — skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uri = (env["COUCHDB_URI"] ?? env["hostname"] ?? "").replace(/\/$/, "");
|
const uri = (env["COUCHDB_URI"] ?? env["hostname"] ?? "").replace(/\/$/, "");
|
||||||
const user = env["COUCHDB_USER"] ?? env["username"] ?? "";
|
const user = env["COUCHDB_USER"] ?? env["username"] ?? "";
|
||||||
|
|||||||
Reference in New Issue
Block a user