diff --git a/.github/workflows/cli-deno-tests.yml b/.github/workflows/cli-deno-tests.yml index b2b4d13..9230910 100644 --- a/.github/workflows/cli-deno-tests.yml +++ b/.github/workflows/cli-deno-tests.yml @@ -17,9 +17,48 @@ permissions: contents: read 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: + needs: prepare runs-on: ubuntu-latest timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + task: ${{ fromJson(needs.prepare.outputs.task_matrix) }} steps: - name: Checkout uses: actions/checkout@v4 @@ -64,7 +103,7 @@ jobs: LIVESYNC_DOCKER_MODE: native LIVESYNC_CLI_RETRY: 3 run: | - TASK="${{ github.event_name == 'workflow_dispatch' && inputs.test_task || 'test' }}" + TASK="${{ matrix.task }}" echo "[INFO] Running Deno task: $TASK" deno task "$TASK" diff --git a/src/apps/cli/testdeno/deno.json b/src/apps/cli/testdeno/deno.json index c056f9c..7f0d035 100644 --- a/src/apps/cli/testdeno/deno.json +++ b/src/apps/cli/testdeno/deno.json @@ -1,19 +1,19 @@ { "tasks": { - "test": "deno test -A --no-check test-*.ts", - "test:local": "deno test -A --no-check test-setup-put-cat.ts test-mirror.ts", - "test:push-pull": "deno test -A --no-check test-push-pull.ts", - "test:setup-put-cat": "deno test -A --no-check test-setup-put-cat.ts", - "test:mirror": "deno test -A --no-check test-mirror.ts", - "test:sync-two-local": "deno test -A --no-check test-sync-two-local-databases.ts", - "test:sync-locked-remote": "deno test -A --no-check test-sync-locked-remote.ts", - "test:p2p-host": "deno test -A --no-check test-p2p-host.ts", - "test:p2p-peers": "deno test -A --no-check test-p2p-peers-local-relay.ts", - "test:p2p-sync": "deno test -A --no-check test-p2p-sync.ts", - "test:p2p-three-nodes": "deno test -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:e2e-couchdb": "deno test -A --no-check test-e2e-two-vaults-couchdb.ts", - "test:e2e-matrix": "deno test -A --no-check test-e2e-two-vaults-matrix.ts" + "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: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", + "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 --env-file=.test.env -A --no-check test-sync-locked-remote.ts", + "test:p2p-host": "deno test --env-file=.test.env -A --no-check test-p2p-host.ts", + "test:p2p-peers": "deno test --env-file=.test.env -A --no-check test-p2p-peers-local-relay.ts", + "test:p2p-sync": "deno test --env-file=.test.env -A --no-check test-p2p-sync.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 --env-file=.test.env -A --no-check test-p2p-upload-download-repro.ts", + "test:e2e-couchdb": "deno test --env-file=.test.env -A --no-check test-e2e-two-vaults-couchdb.ts", + "test:e2e-matrix": "deno test --env-file=.test.env -A --no-check test-e2e-two-vaults-matrix.ts" }, "imports": { "@std/assert": "jsr:@std/assert@^1.0.13", diff --git a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts index f1b60f1..6f5244b 100644 --- a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts +++ b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts @@ -1,6 +1,5 @@ import { assert } from "@std/assert"; import { TempDir } from "./helpers/temp.ts"; -import { loadEnvFile } from "./helpers/env.ts"; import { runCli, runCliOrFail, @@ -11,31 +10,29 @@ import { } from "./helpers/cli.ts"; import { applyRemoteSyncSettings, initSettingsFile } from "./helpers/settings.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"; -function requireEnv(env: Record, key: string): string { - const value = env[key]?.trim(); - if (!value) throw new Error(`Required env var is missing: ${key}`); - return value; +function requireEnv(...keys: string[]): string { + for (const key of keys) { + const value = Deno.env.get(key)?.trim(); + if (value) return value; + } + throw new Error(`Required env var is missing: ${keys.join(" or ")}`); } export async function runScenario(remoteType: RemoteType, encrypt: boolean): Promise { - const env = await loadEnvFile(TEST_ENV); const dbSuffix = `${Date.now()}-${Math.floor(Math.random() * 100000)}`; - const couchdbUri = remoteType === "COUCHDB" ? requireEnv(env, "hostname").replace(/\/$/, "") : ""; - const couchdbUser = remoteType === "COUCHDB" ? requireEnv(env, "username") : ""; - const couchdbPassword = remoteType === "COUCHDB" ? requireEnv(env, "password") : ""; - const dbPrefix = remoteType === "COUCHDB" ? requireEnv(env, "dbname") : ""; + const couchdbUri = remoteType === "COUCHDB" ? requireEnv("COUCHDB_URI", "hostname").replace(/\/$/, "") : ""; + const couchdbUser = remoteType === "COUCHDB" ? requireEnv("COUCHDB_USER", "username") : ""; + const couchdbPassword = remoteType === "COUCHDB" ? requireEnv("COUCHDB_PASSWORD", "password") : ""; + const dbPrefix = remoteType === "COUCHDB" ? requireEnv("COUCHDB_DBNAME", "dbname") : ""; const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : ""; - const minioEndpoint = remoteType === "MINIO" ? requireEnv(env, "minioEndpoint").replace(/\/$/, "") : ""; - const minioAccessKey = remoteType === "MINIO" ? requireEnv(env, "accessKey") : ""; - const minioSecretKey = remoteType === "MINIO" ? requireEnv(env, "secretKey") : ""; - const minioBucketBase = remoteType === "MINIO" ? requireEnv(env, "bucketName") : ""; + const minioEndpoint = remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : ""; + const minioAccessKey = remoteType === "MINIO" ? requireEnv("MINIO_ACCESS_KEY", "accessKey") : ""; + const minioSecretKey = remoteType === "MINIO" ? requireEnv("MINIO_SECRET_KEY", "secretKey") : ""; + const minioBucketBase = remoteType === "MINIO" ? requireEnv("MINIO_BUCKET_NAME", "bucketName") : ""; const minioBucket = remoteType === "MINIO" ? `${minioBucketBase}-${dbSuffix}` : ""; const passphrase = "e2e-passphrase"; diff --git a/src/apps/cli/testdeno/test-sync-locked-remote.ts b/src/apps/cli/testdeno/test-sync-locked-remote.ts index 1dfc568..d8b2e3d 100644 --- a/src/apps/cli/testdeno/test-sync-locked-remote.ts +++ b/src/apps/cli/testdeno/test-sync-locked-remote.ts @@ -6,30 +6,26 @@ */ import { assert, assertStringIncludes } from "@std/assert"; -import { join } from "@std/path"; -import { loadEnvFile } from "./helpers/env.ts"; import { TempDir } from "./helpers/temp.ts"; import { runCli } from "./helpers/cli.ts"; import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.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"; -function requireEnv(env: Record, key: string): string { - const value = env[key]?.trim(); - if (!value) { - throw new Error(`Required env var is missing: ${key}`); +function requireEnv(...keys: string[]): string { + for (const key of keys) { + const value = Deno.env.get(key)?.trim(); + 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 () => { - const env = await loadEnvFile(TEST_ENV); - const couchdbUri = requireEnv(env, "hostname").replace(/\/$/, ""); - const couchdbUser = requireEnv(env, "username"); - const couchdbPassword = requireEnv(env, "password"); - const dbPrefix = requireEnv(env, "dbname"); + const couchdbUri = requireEnv("COUCHDB_URI", "hostname").replace(/\/$/, ""); + const couchdbUser = requireEnv("COUCHDB_USER", "username"); + const couchdbPassword = requireEnv("COUCHDB_PASSWORD", "password"); + const dbPrefix = requireEnv("COUCHDB_DBNAME", "dbname"); const dbname = `${dbPrefix}-locked-${Date.now()}-${Math.floor(Math.random() * 100000)}`; await using workDir = await TempDir.create("livesync-cli-locked-test"); diff --git a/src/apps/cli/testdeno/test-sync-two-local-databases.ts b/src/apps/cli/testdeno/test-sync-two-local-databases.ts index c14ee08..5717d40 100644 --- a/src/apps/cli/testdeno/test-sync-two-local-databases.ts +++ b/src/apps/cli/testdeno/test-sync-two-local-databases.ts @@ -23,13 +23,11 @@ * deno test -A test-sync-two-local-databases.ts */ -import { join } from "@std/path"; import { assertEquals, assert } from "@std/assert"; 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 { startCouchdb, stopCouchdb } from "./helpers/docker.ts"; -import { loadEnvFile } from "./helpers/env.ts"; // --------------------------------------------------------------------------- // Load configuration @@ -41,20 +39,7 @@ async function resolveConfig(): Promise<{ password: string; baseDbname: string; } | null> { - let env: Record = {}; - - // 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 env = Deno.env.toObject(); const uri = (env["COUCHDB_URI"] ?? env["hostname"] ?? "").replace(/\/$/, ""); const user = env["COUCHDB_USER"] ?? env["username"] ?? "";