diff --git a/.circleci/README.md b/.circleci/README.md index becc455b4..50fae8dfc 100644 --- a/.circleci/README.md +++ b/.circleci/README.md @@ -7,10 +7,13 @@ CircleCI validates **arangojs** using one parameterized job, `**node-test`**, wi **Node.js:** `package.json` `engines.node` requires **>=20**; CircleCI executors exercise the **current LTS pair** (**22.x** and **24.x**). -| Pipeline parameter `docker-img` | Workflows | DB / coverage | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | -| **Empty** (default) | `**integration-single-topology`**, `**integration-cluster-topology**`, `**integration-http-proto-smoke**`, `**browser-smoke**` | **36** jobs total. | -| **Non-empty** | **`integration-tests-given-db-image`**, **`integration-http-proto-smoke-given-db-image`**, **`browser-smoke-given-db-image`** | **19** jobs total (16 + 2 + 1); same split as the default pipeline | +| Pipeline parameter | Workflows | Coverage | +| --- | --- | --- | +| **`docker-img` empty**, **`browser-tests` false** (default) | `integration-single-topology`, `integration-cluster-topology`, `integration-http-proto-smoke`, `browser-smoke` | **36** jobs | +| **`docker-img` set**, **`browser-tests` false** | `integration-tests-given-db-image`, `integration-http-proto-smoke-given-db-image`, `browser-smoke-given-db-image` | **19** jobs (16 + 2 + 1) | +| **`browser-tests` true** and **`docker-img` set** | `browser-tests` only | **2** jobs (single + cluster) | + +Set **`browser-tests`** to skip every other workflow, including smoke. ### Secrets and context @@ -26,7 +29,7 @@ All integration and browser jobs attach **`context: docker-hub`**. | Job | Executor | Resource class | | --- | -------- | -------------- | | **`node-test`** | `n22` / `n24` | `arangodb/medium-arm64-privileged` | -| **`browser-smoke`** | `n24-browser` (`cimg/node:24.4`) | `arangodb/medium-amd64-privileged` | +| **`browser-smoke`** / **`browser-test`** | `n24-browser` (`cimg/node:24.16`) | `arangodb/medium-amd64-privileged` | - **`setup-docker`** — install Docker CLI, start in-container `dockerd` (DinD). - **`login-docker-hub`** — before **`start-db`** (avoids anonymous pull rate limits). @@ -153,7 +156,7 @@ Same fixed stack as default workflow (C), with **`docker-img`** as the pipeline --- -## 4) Shared jobs: `node-test` and `browser-smoke` +## 4) Shared jobs: `node-test`, `browser-smoke`, and `browser-test` ### `node-test` @@ -176,6 +179,19 @@ Same Docker setup and **`login-docker-hub`** → **`start-db`** (single, HTTP) a 2. `npm install`, `npm run build` (`PUPPETEER_SKIP_DOWNLOAD=true`) 3. `node smoke-test.mjs` with `PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable`, `ARANGO_PROXY_TARGET=172.28.0.1:8529` +### `browser-test` + +Manual-only. Same Docker setup and Chrome install as smoke, then `npm run test:browser`. + +| Topology | `start-db` | Proxy targets | Load balancing | +| --- | --- | --- | --- | +| **single** | `STARTER_MODE=single` | `172.28.0.1:8529` | driver default | +| **cluster** | `STARTER_MODE=cluster` | `8529,8539,8549` | `TEST_ARANGO_LOAD_BALANCING_STRATEGY=ROUND_ROBIN` | + +The runner serves the Mocha page on `127.0.0.1:8559` and extra coordinator proxies on `8560` / `8561` (CORS allowed from the page origin). `ARANGO_RELEASE` comes from `docker-img`. `CI=true`. + +The job has a 60-minute ceiling (cluster suite is slower). Node-only tests are excluded in `browser-test.mjs`. + --- ## 5) Operational usage @@ -187,6 +203,13 @@ Same Docker setup and **`login-docker-hub`** → **`start-db`** (single, HTTP) a ### Custom DB image -1. **Trigger Pipeline** → set **`docker-img`** to the full image reference. +1. **Trigger Pipeline** → set **`docker-img`** to the full image reference. Leave **`browser-tests`** false. 2. Runs **three workflows** in parallel (**19** jobs): **`integration-tests-given-db-image`** (16), **`integration-http-proto-smoke-given-db-image`** (2), **`browser-smoke-given-db-image`** (1). +### Browser integration suite only + +1. **Trigger Pipeline**. +2. Set **`browser-tests`** to **true**. +3. Set **`docker-img`** to the ArangoDB image (same parameter as the rest of CI). +4. Only the **`browser-tests`** workflow runs (**2** jobs: `browser-tests-single` and `browser-tests-cluster`). Smoke and Node jobs do not run. + diff --git a/.circleci/config.yml b/.circleci/config.yml index f48fd1c82..ab4f642c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,6 +4,12 @@ parameters: docker-img: type: string default: '' + browser-tests: + description: > + Run only the browser integration suite against docker-img. Every other + workflow is skipped while this is true. + type: boolean + default: false commands: timeout: @@ -215,11 +221,73 @@ jobs: PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome-stable ARANGO_PROXY_TARGET: 172.28.0.1:8529 + browser-test: + parameters: + docker-img: + type: string + topology: + type: enum + enum: ['single', 'cluster'] + default: 'single' + executor: n24-browser + steps: + - timeout: + duration: '60m' + - checkout + - setup-docker + - login-docker-hub + - start-db: + docker-img: <> + topology: <> + ssl: 'false' + - run: + name: Install Google Chrome (AMD64) + command: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends curl ca-certificates \ + fonts-liberation \ + libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libcairo2 libcups2t64 \ + libdbus-1-3 libdrm2 libexpat1 libfontconfig1 libgbm1 libglib2.0-0t64 \ + libgtk-3-0t64 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 \ + libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxdamage1 libxext6 \ + libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 + curl -fsSL -o /tmp/google-chrome.deb \ + https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo apt-get install -y /tmp/google-chrome.deb + rm /tmp/google-chrome.deb + google-chrome-stable --version + - run: + name: Install dependencies and build + command: | + sudo apt-get install -y --no-install-recommends jq + npm install + environment: + PUPPETEER_SKIP_DOWNLOAD: 'true' + - run: + name: Run browser integration tests + command: | + set -euo pipefail + if [ "<>" = "cluster" ]; then + export ARANGO_PROXY_TARGET="172.28.0.1:8529,172.28.0.1:8539,172.28.0.1:8549" + export TEST_ARANGO_LOAD_BALANCING_STRATEGY=ROUND_ROBIN + export BROWSER_TEST_TIMEOUT_MS=2400000 + else + export ARANGO_PROXY_TARGET=172.28.0.1:8529 + fi + export PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable + export ARANGO_RELEASE="<>" + export CI=true + npm run test:browser + no_output_timeout: 40m + workflows: # 16 jobs: single-server only. HTTP/1.1 (default http_proto). 2 DB × 2 Node × 2 SSL × 2 module systems. integration-single-topology: when: - not: <> + and: + - not: <> + - not: <> jobs: - node-test: name: single-<>-ssl<>-<>-312 @@ -265,7 +333,9 @@ workflows: # 16 jobs: cluster only. HTTP/1.1 (default http_proto). Same matrix as single. integration-cluster-topology: when: - not: <> + and: + - not: <> + - not: <> jobs: - node-test: name: cluster-<>-ssl<>-<>-312 @@ -311,7 +381,9 @@ workflows: # 4 jobs: Enterprise 3.12.9, Node 22, SSL, single and cluster, CJS and ESM. integration-3129: when: - not: <> + and: + - not: <> + - not: <> jobs: - node-test: name: <>-n22-ssltrue-<>-3.12.9 @@ -334,7 +406,9 @@ workflows: # 2 jobs: HTTP/1.1 vs HTTP/2 on a fixed "best" stack (Enterprise 3.12, Node 24, ESM, TLS, single). integration-http-proto-smoke: when: - not: <> + and: + - not: <> + - not: <> jobs: - node-test: name: http-proto-h1-smoke @@ -364,7 +438,9 @@ workflows: # 2 jobs: browser bundling smoke (Puppeteer) on pinned DB images. browser-smoke: when: - not: <> + and: + - not: <> + - not: <> jobs: - browser-smoke: name: browser-smoke-312 @@ -383,7 +459,10 @@ workflows: # Manual pipeline (docker-img set): driver matrix on your image (16 jobs). integration-tests-given-db-image: - when: <> + when: + and: + - <> + - not: <> jobs: - node-test: name: <>-<>-ssl<>-<> @@ -410,7 +489,10 @@ workflows: # Manual pipeline (docker-img set): HTTP/1.1 vs HTTP/2 smoke (2 jobs). integration-http-proto-smoke-given-db-image: - when: <> + when: + and: + - <> + - not: <> jobs: - node-test: name: http-proto-h1-smoke @@ -439,7 +521,10 @@ workflows: # Manual pipeline (docker-img set): browser bundling smoke (1 job). browser-smoke-given-db-image: - when: <> + when: + and: + - <> + - not: <> jobs: - browser-smoke: name: browser-smoke @@ -447,4 +532,25 @@ workflows: filters: branches: ignore: stable - docker-img: <> \ No newline at end of file + docker-img: <> + + # Manual pipeline: browser integration suite only (2 jobs: single + cluster). + # Trigger Pipeline → browser-tests=true and docker-img=. + browser-tests: + when: + and: + - <> + - <> + jobs: + - browser-test: + name: browser-tests-<> + context: docker-hub + filters: + branches: + ignore: stable + docker-img: <> + matrix: + parameters: + topology: + - 'single' + - 'cluster' diff --git a/browser-test.mjs b/browser-test.mjs new file mode 100644 index 000000000..6cb158110 --- /dev/null +++ b/browser-test.mjs @@ -0,0 +1,311 @@ +import { readdir } from "node:fs/promises"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import esbuild from "esbuild"; + +const require = createRequire(import.meta.url); +const express = require("express"); +const proxy = require("express-http-proxy"); +const puppeteer = require("puppeteer"); + +const root = path.dirname(fileURLToPath(import.meta.url)); +const testDirectory = path.join(root, "src", "test"); +const port = Number(process.env.BROWSER_TEST_PORT) || 8559; +const origin = `http://127.0.0.1:${port}`; +const proxyTargets = (process.env.ARANGO_PROXY_TARGET || "127.0.0.1:8529") + .split(",") + .map((target) => target.trim()) + .filter(Boolean); +if (!proxyTargets.length) { + throw new Error("ARANGO_PROXY_TARGET must list at least one host:port"); +} +const testOrigins = proxyTargets.map( + (_target, index) => `http://127.0.0.1:${port + index}`, +); +const totalTimeoutMs = + Number(process.env.BROWSER_TEST_TIMEOUT_MS) || 20 * 60 * 1000; + +const excludedTests = new Map([ + [ + "13-bulk-imports.ts", + "uses Node.js Buffer inputs; browser Blob coverage belongs in a dedicated test", + ], + ["22-foxx-api.ts", "loads Foxx zip fixtures with Node.js fs and path"], + ["33-content-length.ts", "contains Node.js Buffer/content-length assertions"], + [ + "34-agent-options-undici.ts", + "tests the Node.js-only undici agentOptions path", + ], + [ + "36-retrying-connection-errors.ts", + "tests Node.js/undici system-error shapes", + ], +]); + +const requestedTests = (process.env.BROWSER_TEST_FILES || "") + .split(",") + .map((name) => name.trim()) + .filter(Boolean) + .map((name) => (name.endsWith(".ts") ? name : `${name}.ts`)); + +const allTestFiles = (await readdir(testDirectory)) + .filter((name) => /^\d.*\.ts$/.test(name)) + .sort(); +const compatibleTestFiles = allTestFiles.filter( + (name) => !excludedTests.has(name), +); +const testFiles = requestedTests.length + ? compatibleTestFiles.filter((name) => + requestedTests.some((requested) => name.startsWith(requested)), + ) + : compatibleTestFiles; + +if (!testFiles.length) { + throw new Error( + `No browser-compatible tests matched BROWSER_TEST_FILES=${JSON.stringify( + process.env.BROWSER_TEST_FILES || "", + )}`, + ); +} + +const browserEnvironment = { + ARANGOJS_DEVEL_VERSION: process.env.ARANGOJS_DEVEL_VERSION || "", + ARANGO_RELEASE: process.env.ARANGO_RELEASE || "", + ARANGO_VERSION: process.env.ARANGO_VERSION || "", + ARANGOJS_VERSION: require("./package.json").version, + CI: process.env.CI || "", + TEST_ARANGODB_URL: testOrigins.join(","), + TEST_ARANGO_LOAD_BALANCING_STRATEGY: + process.env.TEST_ARANGO_LOAD_BALANCING_STRATEGY || "", + TEST_ARANGO_VECTOR_INDEX: process.env.TEST_ARANGO_VECTOR_INDEX || "", +}; +const entryPoint = testFiles + .map((name) => `import ${JSON.stringify(`./src/test/${name}`)};`) + .join("\n"); + +console.log( + `Running ${testFiles.length} browser-compatible test files against ${proxyTargets.join(", ")}`, +); +for (const [name, reason] of excludedTests) { + if (allTestFiles.includes(name)) console.log(`Skipping ${name}: ${reason}`); +} + +const bundle = await esbuild.build({ + stdin: { + contents: entryPoint, + loader: "js", + resolveDir: root, + sourcefile: "browser-test-entry.js", + }, + bundle: true, + format: "esm", + platform: "browser", + write: false, + sourcemap: "inline", + logLevel: "error", + logOverride: { "assign-to-define": "silent" }, + define: { + module: "undefined", + exports: "undefined", + "process.env": JSON.stringify(browserEnvironment), + }, +}); + +const serializeForScript = (value) => + JSON.stringify(value).replaceAll("<", "\\u003c"); +const mochaPath = require.resolve("mocha/mocha.js"); +const app = express(); + +app.get("/browser-tests", (_request, response) => { + response.type("html").send(` + + arangojs browser tests + +
+ + + + +`); +}); +app.get("/browser-tests/mocha.js", (_request, response) => { + response.sendFile(mochaPath); +}); +app.get("/browser-tests/index.js", (_request, response) => { + response.type("js").send(bundle.outputFiles[0].text); +}); +app.get("/favicon.ico", (_request, response) => response.sendStatus(204)); +attachClusterEndpoints(app); +app.use("/", proxy(proxyTargets[0], proxyOptions())); + +function attachClusterEndpoints(application) { + application.use((request, response, next) => { + if (!request.path.endsWith("/_api/cluster/endpoints")) { + next(); + return; + } + // acquireHostList() must keep using the browser-visible proxy URLs. The + // real endpoint response contains Docker-internal coordinator URLs, whose + // response headers are subject to ArangoDB's more restrictive CORS policy. + response.json({ + error: false, + code: 200, + endpoints: testOrigins.map((endpoint) => ({ endpoint })), + }); + }); +} + +function proxyOptions() { + return { + parseReqBody: false, + // ArangoDB emits its own CORS headers whenever the request carries an + // Origin, and its Access-Control-Expose-Headers list omits headers the + // driver reads (e.g. x-arango-queue-time-seconds). Rewrite the list so + // every response header stays readable from the test page. + userResHeaderDecorator(headers) { + return { + ...headers, + "access-control-allow-origin": origin, + "access-control-expose-headers": Object.keys(headers) + .filter((name) => !name.startsWith("access-control-")) + .join(", "), + }; + }, + }; +} + +function listen(application, listenPort) { + return new Promise((resolve, reject) => { + const listener = application.listen(listenPort, "127.0.0.1", () => + resolve(listener), + ); + listener.on("error", reject); + }); +} + +function attachCors(application) { + application.use((request, response, next) => { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader( + "Access-Control-Allow-Headers", + request.headers["access-control-request-headers"] || "*", + ); + response.setHeader( + "Access-Control-Allow-Methods", + "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", + ); + response.setHeader("Access-Control-Max-Age", "86400"); + if (request.method === "OPTIONS") { + response.status(204).end(); + return; + } + next(); + }); +} + +const servers = [await listen(app, port)]; +for (let index = 1; index < proxyTargets.length; index++) { + const extra = express(); + attachCors(extra); + attachClusterEndpoints(extra); + extra.use("/", proxy(proxyTargets[index], proxyOptions())); + servers.push(await listen(extra, port + index)); +} + +let browser; +try { + const launchOptions = { + args: ["--no-sandbox", "--disable-dev-shm-usage"], + protocolTimeout: totalTimeoutMs + 60_000, + }; + if (process.env.PUPPETEER_EXECUTABLE_PATH) { + launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH; + } + browser = await puppeteer.launch(launchOptions); + const page = await browser.newPage(); + page.setDefaultTimeout(totalTimeoutMs); + page.on("console", (message) => { + const output = message.type() === "error" ? console.error : console.log; + output(`[browser] ${message.text()}`); + }); + page.on("pageerror", (error) => console.error("[browser]", error)); + + await page.goto(`${origin}/browser-tests`, { waitUntil: "domcontentloaded" }); + await page.waitForFunction(() => window.__browserTestResult !== null, { + timeout: totalTimeoutMs, + }); + const result = await page.evaluate(() => window.__browserTestResult); + const { stats } = result; + console.log( + `Browser tests: ${stats.passes} passed, ${stats.failures} failed, ` + + `${stats.pending} pending (${stats.tests} total, ${stats.duration}ms)`, + ); + for (const failure of result.failures) { + console.error(`\n${failure.title}\n${failure.stack || failure.message}`); + } + if (!stats.tests) { + console.error("Browser tests failed: Mocha did not run any tests."); + process.exitCode = 1; + } else if (stats.failures) { + process.exitCode = 1; + } +} catch (error) { + console.error("Browser test runner failed:", error); + process.exitCode = 1; +} finally { + if (browser) await browser.close(); + await Promise.all( + servers.map( + (listener) => + new Promise((resolve, reject) => + listener.close((error) => (error ? reject(error) : resolve())), + ), + ), + ); +} diff --git a/package.json b/package.json index d41ed4140..fcdc91f83 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "test": "npm run test:esm", "test:cjs": "mocha --reporter spec --require source-map-support/register --timeout 10000 build/cjs/test", "test:esm": "mocha --reporter spec --require source-map-support/register --timeout 10000 build/esm/test", + "test:browser": "node browser-test.mjs", "pretest": "npm run build", "build": "rm -rf build && mkdir build && npm-run-all build:package --parallel build:cjs build:esm build:dts build:text", "build:package": "cat package.json | jq 'delpaths([[\"private\"],[\"type\"],[\"scripts\"],[\"devDependencies\"],[\"babel\"],[\"browserslist\"]])' | sed 's/build\\///g' | sed -r 's/src\\/([^\"]+)\\.ts/\\1.js/g' > build/package.json", diff --git a/src/test/11-managing-indexes.ts b/src/test/11-managing-indexes.ts index 67372f87a..10fe2c312 100644 --- a/src/test/11-managing-indexes.ts +++ b/src/test/11-managing-indexes.ts @@ -10,8 +10,6 @@ import { waitForNewDatabase, } from "./_integration-timeouts.js"; -const it312 = config.arangoVersion! >= 31200 ? it : it.skip; - /** ArangoDB 3.12.9+ vector index responses may include this (DE-1147). */ const VECTOR_TRAINING_STATES = [ "unusable", @@ -78,12 +76,14 @@ describe("Managing indexes", function () { system.close(); } }); + describe("collection.ensureIndex#vector", function () { let vectorIndexTestsEnabled = false; before(async function () { + if (arangoVersionCode < 31200) this.skip(); const mode = resolveVectorIndexTestMode(); - if (config.arangoVersion! < 31200 || mode === "skip") { + if (mode === "skip") { vectorIndexTestsEnabled = false; return; } @@ -374,8 +374,11 @@ describe("Managing indexes", function () { expect(info).to.have.property("isNewlyCreated", true); }); }); - describe("collection.ensureIndex#mdi", () => { - it312("should create an MDI index", async () => { + describe("collection.ensureIndex#mdi", function () { + before(function () { + if (arangoVersionCode < 31200) this.skip(); + }); + it("should create an MDI index", async () => { const info = await collection.ensureIndex({ type: "mdi", fields: ["x", "y", "z"], @@ -426,8 +429,11 @@ describe("Managing indexes", function () { expect(indexes.filter((i: any) => i.id === index.id).length).to.equal(0); }); }); - describe("collection.ensureIndex#inverted", () => { - it312( + describe("collection.ensureIndex#inverted", function () { + before(function () { + if (arangoVersionCode < 31200) this.skip(); + }); + it( "should create an inverted index with new consolidation policy options", async () => { const info = await collection.ensureIndex({ diff --git a/src/test/26-manipulating-views.ts b/src/test/26-manipulating-views.ts index 7c0da5043..d72fabb68 100644 --- a/src/test/26-manipulating-views.ts +++ b/src/test/26-manipulating-views.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { Database } from "../databases.js"; import { ArangoSearchViewProperties, View } from "../views.js"; +import { fetchArangoVersionCode } from "./_arango-server-version.js"; import { config } from "./_config.js"; import { clusterIntegrationTimeoutMs, @@ -11,20 +12,22 @@ import { // NOTE These tests will not reliably work in a cluster. const describeNLB = config.loadBalancingStrategy === "ROUND_ROBIN" ? describe.skip : describe; -const it312 = config.arangoVersion! >= 31200 ? it : it.skip; describe("Manipulating views", function () { this.timeout(clusterIntegrationTimeoutMs); const name = `testdb_${Date.now()}`; let system: Database, db: Database; let view: View; + let arangoVersionCode: number; before(async () => { system = new Database(config); if (Array.isArray(config.url) && config.loadBalancingStrategy !== "NONE") await system.acquireHostList(); db = await system.createDatabase(name); await waitForNewDatabase(db); + arangoVersionCode = await fetchArangoVersionCode(db); }); + after(async () => { try { await system.dropDatabase(name); @@ -73,29 +76,34 @@ describe("Manipulating views", function () { expect(properties.consolidationIntervalMsec).to.equal(45000); expect(properties.commitIntervalMsec).to.equal(30000); }); - it312( - "should support new consolidation policy options (maxSkewThreshold, minDeletionRatio)", - async () => { - const properties = (await view.updateProperties({ - consolidationPolicy: { - type: "tier", - maxSkewThreshold: 0.5, - minDeletionRatio: 0.6, - }, - })) as ArangoSearchViewProperties; - expect(properties.consolidationPolicy).to.have.property("type", "tier"); - if (properties.consolidationPolicy.type === "tier") { - expect(properties.consolidationPolicy).to.have.property( - "maxSkewThreshold", - 0.5, - ); - expect(properties.consolidationPolicy).to.have.property( - "minDeletionRatio", - 0.6, - ); - } - }, - ); + describe("new consolidation policy options", function () { + before(function () { + if (arangoVersionCode < 31200) this.skip(); + }); + it( + "should support maxSkewThreshold and minDeletionRatio", + async () => { + const properties = (await view.updateProperties({ + consolidationPolicy: { + type: "tier", + maxSkewThreshold: 0.5, + minDeletionRatio: 0.6, + }, + })) as ArangoSearchViewProperties; + expect(properties.consolidationPolicy).to.have.property("type", "tier"); + if (properties.consolidationPolicy.type === "tier") { + expect(properties.consolidationPolicy).to.have.property( + "maxSkewThreshold", + 0.5, + ); + expect(properties.consolidationPolicy).to.have.property( + "minDeletionRatio", + 0.6, + ); + } + }, + ); + }); }); describe("view.replaceProperties", () => { it("should overwrite properties", async () => { diff --git a/src/test/32-access-tokens.ts b/src/test/32-access-tokens.ts index e9663ca6b..38e31e0f6 100644 --- a/src/test/32-access-tokens.ts +++ b/src/test/32-access-tokens.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { Database } from "../databases.js"; import { isArangoError } from "../errors.js"; +import { fetchArangoVersionCode } from "./_arango-server-version.js"; import { config, isClusterRuntime } from "./_config.js"; import { accessTokenIdsEqual, @@ -36,20 +37,19 @@ async function withUnauthorizedRetry(fn: () => Promise): Promise { throw last instanceof Error ? last : new Error(String(last)); } -// Access tokens require ArangoDB 3.12+ -const describe312 = config.arangoVersion! >= 31200 ? describe : describe.skip; - -describe312("Access Tokens", function () { +describe("Access Tokens", function () { this.timeout(clusterIntegrationTimeoutMs); let system: Database; const testUsername = `testuser_${Date.now()}`; const testPassword = "testpass123"; - before(async () => { + before(async function () { system = new Database(config); if (Array.isArray(config.url) && config.loadBalancingStrategy !== "NONE") { await system.acquireHostList(); } + // Access tokens require ArangoDB 3.12+ + if ((await fetchArangoVersionCode(system)) < 31200) this.skip(); await system.createUser(testUsername, testPassword); await waitForUserPropagated(system, testUsername); });