- Add default test environment

- Fixed to use environment by APIs
- Make test parallel
This commit is contained in:
vorotamoroz
2026-05-08 03:04:14 +00:00
parent e81f023943
commit a9c87fa52e
5 changed files with 79 additions and 62 deletions
+40 -1
View File
@@ -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"
+14 -14
View File
@@ -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"] ?? "";