@zukhruf/testing 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/dist/databases/mariadb.d.ts +16 -0
- package/dist/databases/mariadb.d.ts.map +1 -0
- package/dist/databases/mariadb.js +13 -0
- package/dist/databases/mariadb.js.map +1 -0
- package/dist/http/http-server.d.ts +13 -2
- package/dist/http/http-server.d.ts.map +1 -1
- package/dist/http/http-server.js +28 -8
- package/dist/http/http-server.js.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zukhruf/testing
|
|
2
2
|
|
|
3
|
-
Disposable fixtures for integration tests on Node.js: Docker containers and database servers, SQLite and DuckDB databases, BigQuery datasets, HTTP servers and controlled streams. Each acquisition returns a handle that `await using` cleans up, also when the test fails. A supervisor removes what a killed test run left behind.
|
|
3
|
+
Disposable fixtures for integration tests on Node.js: Docker containers and database servers, SQLite and DuckDB databases, BigQuery datasets, HTTP servers and controlled streams. Each acquisition returns a handle that `await using` cleans up, also when the test fails. A background server has no handle: it lives until the process exits. A supervisor removes what a killed test run left behind.
|
|
4
4
|
|
|
5
5
|
The words in these documents have one meaning each. See the glossary in [CONTEXT.md](./CONTEXT.md).
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ Each area has its own import path, so a test loads only the drivers it uses. The
|
|
|
14
14
|
| `@zukhruf/testing/docker` | `Docker`, `Container`, `ServiceContainer`, `DockerVolume`, `DockerDirectory`, `TestRun`, `skipWithoutDocker` | Docker CLI |
|
|
15
15
|
| `@zukhruf/testing/postgres` | `Postgres` | Docker CLI |
|
|
16
16
|
| `@zukhruf/testing/mysql` | `Mysql` | Docker CLI |
|
|
17
|
+
| `@zukhruf/testing/mariadb` | `Mariadb` | Docker CLI |
|
|
17
18
|
| `@zukhruf/testing/sqlserver` | `SqlServer`, `SQL_SERVER_FULL_IMAGE`, `SQL_SERVER_EDGE_IMAGE` | Docker CLI, `mssql` |
|
|
18
19
|
| `@zukhruf/testing/clickhouse` | `ClickHouse` | Docker CLI |
|
|
19
20
|
| `@zukhruf/testing/sqlite` | `Sqlite` | |
|
|
@@ -103,7 +104,7 @@ To pass the run to another container API, spread `docker.defaults`: it holds the
|
|
|
103
104
|
|
|
104
105
|
## Databases
|
|
105
106
|
|
|
106
|
-
`Postgres`, `Mysql` and `SqlServer` take the `docker` to run on. Each `database()` call creates a fresh database on a shared server; disposal drops it. `start()` creates a dedicated server; disposal stops it.
|
|
107
|
+
`Postgres`, `Mysql`, `Mariadb` and `SqlServer` take the `docker` to run on. Each `database()` call creates a fresh database on a shared server; disposal drops it. `start()` creates a dedicated server; disposal stops it.
|
|
107
108
|
|
|
108
109
|
```ts
|
|
109
110
|
import { Postgres } from '@zukhruf/testing/postgres';
|
|
@@ -116,7 +117,7 @@ test('stores a record', { skip }, async () => {
|
|
|
116
117
|
});
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
The handle has `connectionString`, `host`, `port`, `user`, `password`, `database`, `image` and `containerId`. `Mysql` handles also have `query(sql)`. Without an image, `SqlServer` picks Azure SQL Edge on an ARM engine and SQL Server 2022 elsewhere. `ClickHouse` takes an explicit `image` and starts a dedicated server, so a test can create server-wide users and functions.
|
|
120
|
+
The handle has `connectionString`, `host`, `port`, `user`, `password`, `database`, `image` and `containerId`. `Mysql` and `Mariadb` handles also have `query(sql)`. Without an image, `Mysql` runs `mysql:lts` and `Mariadb` runs `mariadb:lts`. MariaDB 11 and later have no `mysql` client, so use `Mariadb` for a MariaDB image. A `Mariadb` connection string starts with `mariadb://`, because the `mariadb` driver accepts no other scheme. Without an image, `SqlServer` picks Azure SQL Edge on an ARM engine and SQL Server 2022 elsewhere. `ClickHouse` takes an explicit `image` and starts a dedicated server, so a test can create server-wide users and functions.
|
|
120
121
|
|
|
121
122
|
`Sqlite` creates a file-backed database in a temporary directory, with a native `DatabaseSync` connection. Disposal closes the connection and removes the directory, including WAL files. `writeLock(path, durationMs)` holds a write lock from another process, which releases it after `durationMs`, even while the caller blocks in a synchronous write.
|
|
122
123
|
|
|
@@ -128,6 +129,20 @@ The handle has `connectionString`, `host`, `port`, `user`, `password`, `database
|
|
|
128
129
|
|
|
129
130
|
`new HttpServer().start(handler)` listens on `127.0.0.1` on a free port and resolves once it listens. The handle has `origin` and the native `server`. Disposal closes active connections and waits for the server to close.
|
|
130
131
|
|
|
132
|
+
Some modules read an origin when they load, so every test in the file uses the same server. `background(handler)` starts a background server for them. It listens until the process exits and never keeps the process running, also while a connection to it is open. It returns only `origin`, because nothing disposes it. Use `start` when only one test reads the origin.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { HttpServer } from '@zukhruf/testing/http';
|
|
136
|
+
|
|
137
|
+
const keys = await new HttpServer().background((request, response) => {
|
|
138
|
+
response.end(JSON.stringify(jwks));
|
|
139
|
+
});
|
|
140
|
+
process.env.KEYS_URL = keys.origin;
|
|
141
|
+
const { verify } = await import('./verify.ts');
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The handler is a Node.js request listener. A web framework gives one for its fetch handler: `getRequestListener((request) => app.fetch(request))` from `@hono/node-server`, or `toNodeHandler((request) => auth.handler(request))` from `better-auth/node`. The handler can be async. When it rejects, the rejection stays unhandled and fails the process, as a throw does. The server never answers for a handler that failed. A handler that needs the origin can read a `const` that the test declares after the acquisition: no request arrives before the test gives the origin to a client.
|
|
145
|
+
|
|
131
146
|
`StreamHarness` gives a controlled producer, `source<T>()`, with `enqueue`, `close`, `error` and a read-only `state`, and a disposable `reader(stream)` that locks a stream without reading ahead. `collectUntilError()` returns the remaining chunks and either `completed` or `errored` with the error.
|
|
132
147
|
|
|
133
148
|
## Waiting
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DatabaseOptions } from './database.ts';
|
|
2
|
+
import { type MysqlFamilyDatabase, MysqlFamilyServer } from './mysql-family-server.ts';
|
|
3
|
+
export type { Database, DatabaseOptions } from './database.ts';
|
|
4
|
+
export type MariadbOptions = DatabaseOptions;
|
|
5
|
+
export type MariadbDatabase = MysqlFamilyDatabase;
|
|
6
|
+
export declare class Mariadb extends MysqlFamilyServer {
|
|
7
|
+
/** MariaDB 11 and later ship only the mariadb client, not mysql. */
|
|
8
|
+
protected readonly client = "mariadb";
|
|
9
|
+
protected readonly scheme = "mariadb";
|
|
10
|
+
constructor({ image, ...options }: MariadbOptions);
|
|
11
|
+
protected environment(password: string, database: string): {
|
|
12
|
+
MARIADB_ROOT_PASSWORD: string;
|
|
13
|
+
MARIADB_DATABASE: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=mariadb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb.d.ts","sourceRoot":"","sources":["../../src/databases/mariadb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EACL,KAAK,mBAAmB,EACxB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/D,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAElD,qBAAa,OAAQ,SAAQ,iBAAiB;IAC5C,oEAAoE;IACpE,mBAA4B,MAAM,aAAa;IAC/C,mBAA4B,MAAM,aAAa;gBAEnC,EAAE,KAAqB,EAAE,GAAG,OAAO,EAAE,EAAE,cAAc;cAI9C,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;CAGlE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MysqlFamilyServer, } from "./mysql-family-server.js";
|
|
2
|
+
export class Mariadb extends MysqlFamilyServer {
|
|
3
|
+
/** MariaDB 11 and later ship only the mariadb client, not mysql. */
|
|
4
|
+
client = 'mariadb';
|
|
5
|
+
scheme = 'mariadb';
|
|
6
|
+
constructor({ image = 'mariadb:lts', ...options }) {
|
|
7
|
+
super({ ...options, image });
|
|
8
|
+
}
|
|
9
|
+
environment(password, database) {
|
|
10
|
+
return { MARIADB_ROOT_PASSWORD: password, MARIADB_DATABASE: database };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=mariadb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb.js","sourceRoot":"","sources":["../../src/databases/mariadb.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAQlC,MAAM,OAAO,OAAQ,SAAQ,iBAAiB;IAC5C,oEAAoE;IACxC,MAAM,GAAG,SAAS,CAAC;IACnB,MAAM,GAAG,SAAS,CAAC;IAE/C,YAAY,EAAE,KAAK,GAAG,aAAa,EAAE,GAAG,OAAO,EAAkB;QAC/D,KAAK,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/B,CAAC;IAEkB,WAAW,CAAC,QAAgB,EAAE,QAAgB;QAC/D,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAC;IACzE,CAAC;CACF"}
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* Answers one request. It may be async: a rejection stays unhandled and fails
|
|
4
|
+
* the process, as a throw in a synchronous handler does.
|
|
5
|
+
*/
|
|
6
|
+
export type HttpHandler = (request: IncomingMessage, response: ServerResponse) => unknown;
|
|
2
7
|
export interface HttpServerHandle extends AsyncDisposable {
|
|
3
8
|
origin: string;
|
|
4
9
|
server: Server;
|
|
5
10
|
/** Stop accepting requests and close active HTTP connections. */
|
|
6
11
|
cleanup: () => Promise<void>;
|
|
7
12
|
}
|
|
13
|
+
/** A server whose scope is the process: nothing disposes it. */
|
|
14
|
+
export interface BackgroundHttpServer {
|
|
15
|
+
origin: string;
|
|
16
|
+
}
|
|
8
17
|
/** Each acquisition owns a loopback HTTP server on an available port. */
|
|
9
18
|
export declare class HttpServer {
|
|
10
|
-
start(handler:
|
|
19
|
+
start(handler: HttpHandler): Promise<HttpServerHandle>;
|
|
20
|
+
/** Serves until the process exits and never keeps it running, even while a connection is open. */
|
|
21
|
+
background(handler: HttpHandler): Promise<BackgroundHttpServer>;
|
|
11
22
|
}
|
|
12
23
|
//# sourceMappingURL=http-server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,cAAc,EAEpB,MAAM,WAAW,CAAC;AAEnB;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC;AAEb,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,yEAAyE;AACzE,qBAAa,UAAU;IACf,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsB5D,kGAAkG;IAC5F,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAOtE"}
|
package/dist/http/http-server.js
CHANGED
|
@@ -1,29 +1,49 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
|
-
import { createServer } from 'node:http';
|
|
3
|
+
import { createServer, } from 'node:http';
|
|
4
4
|
/** Each acquisition owns a loopback HTTP server on an available port. */
|
|
5
5
|
export class HttpServer {
|
|
6
6
|
async start(handler) {
|
|
7
7
|
await using resources = new AsyncDisposableStack();
|
|
8
|
-
const server =
|
|
9
|
-
|
|
10
|
-
server.listen(0, '127.0.0.1');
|
|
11
|
-
await listening;
|
|
8
|
+
const server = serve(handler);
|
|
9
|
+
await listen(server);
|
|
12
10
|
resources.defer(async () => {
|
|
13
11
|
const closed = server[Symbol.asyncDispose]();
|
|
14
12
|
server.closeAllConnections();
|
|
15
13
|
await closed;
|
|
16
14
|
});
|
|
17
|
-
const
|
|
18
|
-
assert(address && typeof address === 'object');
|
|
15
|
+
const origin = originOf(server);
|
|
19
16
|
const owned = resources.move();
|
|
20
17
|
const cleanup = () => owned.disposeAsync();
|
|
21
18
|
return {
|
|
22
|
-
origin
|
|
19
|
+
origin,
|
|
23
20
|
server,
|
|
24
21
|
cleanup,
|
|
25
22
|
[Symbol.asyncDispose]: cleanup,
|
|
26
23
|
};
|
|
27
24
|
}
|
|
25
|
+
/** Serves until the process exits and never keeps it running, even while a connection is open. */
|
|
26
|
+
async background(handler) {
|
|
27
|
+
const server = serve(handler);
|
|
28
|
+
server.on('connection', (socket) => socket.unref());
|
|
29
|
+
await listen(server);
|
|
30
|
+
server.unref();
|
|
31
|
+
return { origin: originOf(server) };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function serve(handler) {
|
|
35
|
+
return createServer((request, response) => {
|
|
36
|
+
void handler(request, response);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function listen(server) {
|
|
40
|
+
const listening = once(server, 'listening');
|
|
41
|
+
server.listen(0, '127.0.0.1');
|
|
42
|
+
await listening;
|
|
43
|
+
}
|
|
44
|
+
function originOf(server) {
|
|
45
|
+
const address = server.address();
|
|
46
|
+
assert(address && typeof address === 'object');
|
|
47
|
+
return `http://127.0.0.1:${address.port}`;
|
|
28
48
|
}
|
|
29
49
|
//# sourceMappingURL=http-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,
|
|
1
|
+
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAIL,YAAY,GACb,MAAM,WAAW,CAAC;AAuBnB,yEAAyE;AACzE,MAAM,OAAO,UAAU;IACrB,KAAK,CAAC,KAAK,CAAC,OAAoB;QAC9B,YAAY,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QAErB,SAAS,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7C,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC7B,MAAM,MAAM,CAAC;QACf,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;QAE3C,OAAO;YACL,MAAM;YACN,MAAM;YACN,OAAO;YACP,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO;SAC/B,CAAC;IACJ,CAAC;IAED,kGAAkG;IAClG,KAAK,CAAC,UAAU,CAAC,OAAoB;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QACpD,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IACtC,CAAC;CACF;AAED,SAAS,KAAK,CAAC,OAAoB;IACjC,OAAO,YAAY,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QACxC,KAAK,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,MAAc;IAClC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC9B,MAAM,SAAS,CAAC;AAClB,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc;IAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IACjC,MAAM,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC;IAC/C,OAAO,oBAAoB,OAAO,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zukhruf/testing",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Disposable fixtures for integration tests: Docker containers and database servers, SQLite and DuckDB databases, HTTP servers and streams. A supervisor removes what a killed test run left behind.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"docker",
|
|
20
20
|
"postgres",
|
|
21
21
|
"mysql",
|
|
22
|
+
"mariadb",
|
|
22
23
|
"sql-server",
|
|
23
24
|
"disposable"
|
|
24
25
|
],
|
|
@@ -48,6 +49,11 @@
|
|
|
48
49
|
"import": "./dist/databases/mysql.js",
|
|
49
50
|
"default": "./dist/databases/mysql.js"
|
|
50
51
|
},
|
|
52
|
+
"./mariadb": {
|
|
53
|
+
"types": "./dist/databases/mariadb.d.ts",
|
|
54
|
+
"import": "./dist/databases/mariadb.js",
|
|
55
|
+
"default": "./dist/databases/mariadb.js"
|
|
56
|
+
},
|
|
51
57
|
"./sqlserver": {
|
|
52
58
|
"types": "./dist/databases/sqlserver.d.ts",
|
|
53
59
|
"import": "./dist/databases/sqlserver.js",
|
|
@@ -98,7 +104,9 @@
|
|
|
98
104
|
"@google-cloud/bigquery": "^8.3.1",
|
|
99
105
|
"@types/mssql": "^12.3.0",
|
|
100
106
|
"@types/pg": "^8.23.1",
|
|
107
|
+
"mariadb": "^3.5.4",
|
|
101
108
|
"mssql": "^12.7.4",
|
|
109
|
+
"mysql2": "^3.24.5",
|
|
102
110
|
"pg": "^8.23.1"
|
|
103
111
|
},
|
|
104
112
|
"dependencies": {
|