--- name: testing description: Decision guide for testing in the ar-io-node repo — which test layer to use, how to run it, and which helpers to reach for. Use when writing a new test, picking a layer (unit / property / e2e / auto-verify / parquet integration / load test), running the suite or a single file, debugging a test failure, or when a schema or pipeline change needs cross-source verification. Triggers: "write a test for X", "run the tests", "which test layer", "run e2e", "run auto-verify", "load test the gateway", "test parquet export", "test against a running service". --- # Testing in ar-io-node ## Layer decision Pick the narrowest layer that can fail on the change. | Layer | Lives in | Runner | When to use | |-------|----------|--------|-------------| | **Unit** | `src/**/*.test.ts` | `yarn test` / `yarn test:file ` | Pure logic, SQL wrappers, filters, route handlers (with `supertest`), middleware, data-source composition. Default choice. | | **Property** | `src/**/*.property.test.ts` | same as unit | Invariants: determinism, output shape, distribution, round-trip. Uses `fast-check`. See `src/lib/cdb64.property.test.ts` for pattern. | | **E2E** | `test/end-to-end/*.test.ts` | `yarn test:e2e` | Full request lifecycle through real containers: admin APIs, ArNS, bundler sidecar, webhook delivery, moderation, Prometheus metrics. Requires Docker. | | **Auto-verify** | `src/tests/auto-verify/` | `yarn test:auto-verify:indexing` | Cross-source indexing reconciliation (SQLite / Parquet / bundle-parser / ClickHouse). Use after schema changes to `stable_*` tables, the Parquet export, or ClickHouse `transactions`. | | **Parquet / ClickHouse scripts** | `scripts/tests/parquet/test-*` | bash, run directly | Parquet export correctness & perf, ClickHouse import pipeline, TTL rules loader. Operate against a populated local DB. | | **Load / stability** | `tools/test-chunk-retrieval`, `tools/test-data-retrieval` | bash, run directly | Stress endpoints for FD leaks, ECONNRESET patterns, p99 latency. Needs a running gateway. | | **GraphQL perf probe** | `test/perf/gql-perf` | bash, `GRAPHQL_URL=… ./test/perf/gql-perf` | Ad-hoc timing comparisons across gateway endpoints. | | **Torrent engine contract** | `src/index-swarm/transport/contract.test.ts` | unit always; against a real engine with `INDEX_SWARM_E2E_ENGINE_URL` | The `TorrentTransport` contract. Runs against the in-memory fake in `yarn test`; set the env vars in the file's header to also run it against a real qBittorrent (turn its IP filter off, or its WebSeed test is blocked). Needed for any change to an adapter under `src/index-swarm/transport/`. | **Escalate when a change crosses boundaries.** A unit test on a SQL wrapper is not enough if the change also alters the `stable_*` schema — add or update an auto-verify adapter. A unit test on the Parquet exporter isn't enough if row counts could drift — run `scripts/tests/parquet/test-parquet-export`. ## Running tests | Command | What it does | |---------|--------------| | `yarn test` | All unit+property tests, sequential (`--test-concurrency 1`). | | `yarn test:file src/foo/bar.test.ts` | Single unit test file. Pass multiple paths if needed. | | `yarn test:coverage` | Full suite with HTML + text coverage via c8. | | `yarn test:file:coverage ` | Single file with coverage. | | `yarn test:e2e` | All e2e tests. Builds the core image unless `USE_PREBUILT_IMAGE=true`. | | `yarn test:auto-verify:indexing` | Auto-verify run. Driven by `.env` + `AUTO_VERIFY_*` vars. | | `yarn lint:check` / `yarn lint:fix` | ESLint. | | `yarn duplicate:check` / `yarn deps:ci` | jscpd and madge circular-dep gate. | **Tag filtering** (unit + e2e): wrap test bodies with `isTestFiltered(tags)` from `test/utils.ts` and set `TEST_ONLY_TAGS=slow,integration` or `TEST_SKIP_TAGS=flaky`. CI runs e2e with `TEST_SKIP_TAGS=flaky`. **Single e2e file**: `yarn test:file test/end-to-end/indexing.test.ts` works — `test:file` just forwards args to `node --test`. ## Writing unit tests - **Test runner**: `node:test` (`describe`, `it`, `before`, `after`, `beforeEach`). Imports: `import { describe, it } from 'node:test'; import assert from 'node:assert';` (or `import { strict as assert } from 'node:assert'`). - **Logger — always** `createTestLogger()` from `test/test-logger.ts`. Never `winston.createLogger({ silent: true })`. Test output goes to `logs/test.log` (overwritten each run), not the console. - **SQLite tests**: import the shared handles from `test/sqlite-helpers.ts` (`coreDb`, `dataDb`, `bundlesDb`, `moderationDb`). The helper creates fresh DBs from `test/*-schema.sql` in `before` and truncates all tables in `afterEach`. Do not construct your own `Sqlite` instance. - **Schema files are generated.** If a migration changes a `stable_*` or `new_*` table, regenerate with `./test/dump-test-schemas` (requires populated `data/sqlite/*.db`) and commit the result. - **Stubs & fixtures**: `test/stubs.ts` (`ArweaveChainSourceStub`, manifest streams, ANS-104 bundle stream), `test/mock_files/` (txs, manifests, block id maps), `test/mocks/` (e.g. `mock-redis-token-bucket.ts`). - **HTTP handlers**: mount the router on an Express app and drive it with `supertest`. Pattern in `src/middleware/httpsig.test.ts`, `src/routes/data/handlers.test.ts`. - **Property tests**: `fast-check` is in devDeps. Use `.property.test.ts` suffix to signal intent. Fix a seed when collisions/distribution matter; keep `numRuns` modest so the suite stays under a minute. ## Writing e2e tests - Use `composeUp()` / `cleanDb()` from `test/end-to-end/utils.ts`. It wraps `DockerComposeEnvironment` (testcontainers) around the real `docker-compose.yaml` with sensible defaults and exposes `START_HEIGHT`, `STOP_HEIGHT`, filters, and `ADMIN_API_KEY='secret'`. - `USE_PREBUILT_IMAGE=true` reuses a pre-built `core` image (CI does this). Local runs rebuild from the Dockerfile. - Wait helpers in the same file: `waitForBlocks`, `waitForBundleToBeIndexed`, `waitForTxToBeIndexed`, `waitForDataItemToBeIndexed`, `waitForLogMessage`. Prefer these over hand-rolled polling. - Tear down with `compose.down()` in `after(...)`. Forgetting this leaks containers across runs and tends to manifest as port conflicts on 4000/5432/8123. ## Auto-verify See `docs/auto-verify.md` for the full spec. Key triggers: - Schema change to `stable_blocks` / `stable_transactions` / `stable_data_items` (or their tag tables) → update the matching adapter under `src/tests/auto-verify/sources/` and the canonical types in `src/tests/auto-verify/types.ts`. - Parquet export schema change → same, update `ParquetSource`. - ClickHouse `transactions` schema change → update `ClickHouseSource` and the table-cleanup list in `gateway-control.ts`'s `cleanClickHouseTables`. A silent adapter divergence shows up as `field_mismatch` / `missing_in_source` discrepancies rather than a build error, so the adapters are the first thing to re-verify after a schema change. Run knobs: `AUTO_VERIFY_ITERATIONS=N` to shuffle and sample, `AUTO_VERIFY_FAIL_FAST=true` while iterating, `AUTO_VERIFY_PRESERVE_CACHE=false` only when changing a `data.db` migration. ## Parquet / ClickHouse integration scripts All under `scripts/tests/parquet/`. They assume a populated `data/sqlite/` and run the real export binary: | Script | Use for | |--------|---------| | `test-parquet-export` | Small export + `--verifyCount`. First thing to run after changing `scripts/parquet-export` or a projected column. | | `test-parquet-full` | L1+L2, bigger range. Catches per-partition edge cases. | | `test-parquet-performance` | Sweep partition sizes (5/10/25/50). Use when changing batching. | | `test-timing` / `test-query-timing` | `[TIMING]` breakdowns; use to isolate slow SQL in export. | | `test-data-integrity` | Post-hoc: verify field formats in an export directory. | | `test-clickhouse-integration` | End-to-end export → clickhouse-import. Run after schema or loader changes. | | `test-clickhouse-ttl-rules` | TTL rules loader only (PE-9058 path). | They read config via `scripts/lib/common.sh` (`load_env`, `load_clickhouse_config`). Height ranges are hardcoded to ranges present in the fixture DB — if `data/sqlite/core.db` doesn't cover the range, expect empty results rather than a failure. ## Load / stability tests - `tools/test-chunk-retrieval` — stress `/chunk/{offset}`. Supports `--concurrency`, `--duration`, `--track-fds ` for FD leak detection on Linux. Output includes p50/p95/p99, status code histogram, resource-exhaustion flag (`EMFILE`, `ENFILE`, `ENOMEM`). - `tools/test-data-retrieval` — same shape, for `/raw/{id}` and similar. See `tools/README.md` for flags. Use these when validating a fix for a reported leak or a latency regression, not as part of the normal dev loop. ## Iterating against a running service When a test needs the real service running (load tools, parquet scripts, ad-hoc curl): 1. `yarn service:stop` 2. `rm logs/service.log && touch logs/service.log` (JSONL log; easier to diff when empty) 3. `yarn service:start` 4. Exercise the feature. 5. `yarn service:logs` or tail `logs/service.log`; OTEL spans land in `logs/otel-spans.jsonl`. ## Gotchas - **Tests run sequentially** (`--test-concurrency 1`). Don't add parallelism assumptions; don't rely on test isolation via parallelism. - **`sqlite-helpers.ts` truncates in `afterEach`, not `beforeEach`.** The first test in a file starts with an empty DB from the `before` hook; later tests inherit cleared-but-migrated state. Inserting in `before` is dangerous because `afterEach` will delete it. - **`bundle_formats` is preserved across truncation** (see the `tbl_name != 'bundle_formats'` filter). If a test depends on an empty `bundle_formats`, wipe it explicitly. - **E2E fixtures are byte files in `test/end-to-end/files/`** (BDI, data items). Don't synthesize them at runtime. - **Auto-verify's gateway spawns through `scripts/service`**, which reads `.env`. `BACKGROUND_RETRIEVAL_ORDER` governs bundle reachability during unbundling, not `ON_DEMAND_RETRIEVAL_ORDER` — if unbundling stalls, check trusted-gateway reachability first. - **Coverage reports** land in `coverage/` (c8). Don't commit. - **Single-test focus**: `node:test` has no `.only`. Use `TEST_ONLY_TAGS` or narrow via `yarn test:file `; combine with `--test-name-pattern` if needed (pass through `yarn test:file`).