@ac-kit/integration-test-util 0.1.0
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/LICENSE +28 -0
- package/README.md +28 -0
- package/dist/docker/init-docker-suite.d.ts +27 -0
- package/dist/docker/init-docker-suite.d.ts.map +1 -0
- package/dist/docker/init-docker-suite.js +47 -0
- package/dist/docker/init-docker-suite.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/net/is-tcp-port-open.d.ts +10 -0
- package/dist/net/is-tcp-port-open.d.ts.map +1 -0
- package/dist/net/is-tcp-port-open.js +20 -0
- package/dist/net/is-tcp-port-open.js.map +1 -0
- package/dist/net/try-tcp-connect-n-read-banner.d.ts +17 -0
- package/dist/net/try-tcp-connect-n-read-banner.d.ts.map +1 -0
- package/dist/net/try-tcp-connect-n-read-banner.js +43 -0
- package/dist/net/try-tcp-connect-n-read-banner.js.map +1 -0
- package/dist/net/try-tls-connect-n-read-banner.d.ts +16 -0
- package/dist/net/try-tls-connect-n-read-banner.d.ts.map +1 -0
- package/dist/net/try-tls-connect-n-read-banner.js +42 -0
- package/dist/net/try-tls-connect-n-read-banner.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Anthony Champagne and contributors
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @ac-kit/integration-test-util
|
|
2
|
+
|
|
3
|
+
Test helpers that drive and probe **external** services: starting a Docker
|
|
4
|
+
container for a suite, and checking that a TCP or TLS endpoint is up and
|
|
5
|
+
speaking the protocol you expect.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { initDockerSuite, isTcpPortOpen } from "@ac-kit/integration-test-util";
|
|
9
|
+
|
|
10
|
+
// Registers beforeAll/afterAll/afterEach hooks covering the whole container
|
|
11
|
+
// lifecycle: build the image, stop and remove the container between tests.
|
|
12
|
+
const suite = initDockerSuite("./fixtures/postfix");
|
|
13
|
+
|
|
14
|
+
await isTcpPortOpen(25, "127.0.0.1");
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Also exposes `tryTcpConnectAndReadBanner` and `tryTlsConnectAndReadBanner`, for
|
|
18
|
+
waiting until a server is not merely listening but actually answering.
|
|
19
|
+
|
|
20
|
+
Separate from `@ac-kit/test-util` on purpose: this package is free to take
|
|
21
|
+
heavier dependencies because only a handful of suites need it, while
|
|
22
|
+
`test-util` must stay light enough for every suite in the tree.
|
|
23
|
+
|
|
24
|
+
Node only.
|
|
25
|
+
|
|
26
|
+
See the
|
|
27
|
+
[generated API docs](https://anthochamp.github.io/node-essentials/api/integration-test-util/)
|
|
28
|
+
for the full reference.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type InitDockerSuiteOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Prefix for the container name.
|
|
4
|
+
*
|
|
5
|
+
* Defaults to "test-".
|
|
6
|
+
*/
|
|
7
|
+
containerNamePrefix?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Docker context to switch to for all suite operations.
|
|
10
|
+
*
|
|
11
|
+
* Defaults to "default".
|
|
12
|
+
*/
|
|
13
|
+
contextName?: string;
|
|
14
|
+
};
|
|
15
|
+
export type DockerSuiteContext = {
|
|
16
|
+
containerName: string;
|
|
17
|
+
containerImageName: string;
|
|
18
|
+
stopContainer: () => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Registers beforeAll / afterAll / afterEach hooks that handle the full Docker
|
|
22
|
+
* lifecycle for a container test suite: - saves and restores the active Docker
|
|
23
|
+
* context - builds the image from srcPath before all tests - removes the image
|
|
24
|
+
* after all tests - stops and removes the container after each test
|
|
25
|
+
*/
|
|
26
|
+
export declare function initDockerSuite(srcPath: string, options?: InitDockerSuiteOptions): DockerSuiteContext;
|
|
27
|
+
//# sourceMappingURL=init-docker-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-docker-suite.d.ts","sourceRoot":"","sources":["../../src/docker/init-docker-suite.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,sBAAsB,GAAG;IACpC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,sBAAsB,GAC9B,kBAAkB,CAqCpB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { dockerBuildxBuild, dockerContainerRm, dockerContextShow, dockerContextUse, dockerImageRm, } from "@ac-kit/cmd-docker";
|
|
2
|
+
import { getRandomBytes } from "@ac-kit/crypto-random";
|
|
3
|
+
import { afterAll, afterEach, beforeAll } from "vitest";
|
|
4
|
+
/**
|
|
5
|
+
* Registers beforeAll / afterAll / afterEach hooks that handle the full Docker
|
|
6
|
+
* lifecycle for a container test suite: - saves and restores the active Docker
|
|
7
|
+
* context - builds the image from srcPath before all tests - removes the image
|
|
8
|
+
* after all tests - stops and removes the container after each test
|
|
9
|
+
*/
|
|
10
|
+
export function initDockerSuite(srcPath, options) {
|
|
11
|
+
const prefix = options?.containerNamePrefix ?? "test-";
|
|
12
|
+
const contextName = options?.contextName ?? "default";
|
|
13
|
+
const containerName = `${prefix}${Buffer.from(getRandomBytes(20)).toString("hex")}`;
|
|
14
|
+
const containerImageName = `${containerName}-img`;
|
|
15
|
+
let initialContext;
|
|
16
|
+
async function stopContainer() {
|
|
17
|
+
try {
|
|
18
|
+
await dockerContainerRm([containerName], { force: true });
|
|
19
|
+
}
|
|
20
|
+
catch { }
|
|
21
|
+
}
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
initialContext = await dockerContextShow();
|
|
24
|
+
await dockerContextUse(contextName);
|
|
25
|
+
await stopContainer();
|
|
26
|
+
try {
|
|
27
|
+
await dockerImageRm([containerImageName], { force: true });
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
await dockerBuildxBuild(srcPath, { tags: [containerImageName] });
|
|
31
|
+
});
|
|
32
|
+
afterAll(async () => {
|
|
33
|
+
try {
|
|
34
|
+
await dockerImageRm([containerImageName], { force: true });
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
try {
|
|
38
|
+
await dockerContextUse(initialContext);
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
});
|
|
42
|
+
afterEach(async () => {
|
|
43
|
+
await stopContainer();
|
|
44
|
+
});
|
|
45
|
+
return { containerName, containerImageName, stopContainer };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=init-docker-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-docker-suite.js","sourceRoot":"","sources":["../../src/docker/init-docker-suite.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAwBxD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC9B,OAAe,EACf,OAAgC;IAEhC,MAAM,MAAM,GAAG,OAAO,EAAE,mBAAmB,IAAI,OAAO,CAAC;IACvD,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,SAAS,CAAC;IACtD,MAAM,aAAa,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IACpF,MAAM,kBAAkB,GAAG,GAAG,aAAa,MAAM,CAAC;IAClD,IAAI,cAAsB,CAAC;IAE3B,KAAK,UAAU,aAAa;QAC3B,IAAI,CAAC;YACJ,MAAM,iBAAiB,CAAC,CAAC,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED,SAAS,CAAC,KAAK,IAAI,EAAE;QACpB,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;QAC3C,MAAM,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,aAAa,EAAE,CAAC;QACtB,IAAI,CAAC;YACJ,MAAM,aAAa,CAAC,CAAC,kBAAkB,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,MAAM,iBAAiB,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;QACnB,IAAI,CAAC;YACJ,MAAM,aAAa,CAAC,CAAC,kBAAkB,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,IAAI,CAAC;YACJ,MAAM,gBAAgB,CAAC,cAAc,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,CAAC;AAC7D,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test if a TCP port is open on a given host.
|
|
3
|
+
*
|
|
4
|
+
* @param port - The TCP port to test.
|
|
5
|
+
* @param host - The host to test the port on. Defaults to `localhost`.
|
|
6
|
+
* @returns A promise that resolves to `true` if the port is open, or `false` if
|
|
7
|
+
* it is closed or unreachable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isTcpPortOpen(port: number, host?: string): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=is-tcp-port-open.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-tcp-port-open.d.ts","sourceRoot":"","sources":["../../src/net/is-tcp-port-open.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAS3E"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createConnection } from "node:net";
|
|
2
|
+
/**
|
|
3
|
+
* Test if a TCP port is open on a given host.
|
|
4
|
+
*
|
|
5
|
+
* @param port - The TCP port to test.
|
|
6
|
+
* @param host - The host to test the port on. Defaults to `localhost`.
|
|
7
|
+
* @returns A promise that resolves to `true` if the port is open, or `false` if
|
|
8
|
+
* it is closed or unreachable.
|
|
9
|
+
*/
|
|
10
|
+
export function isTcpPortOpen(port, host) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const socket = createConnection({ host, port });
|
|
13
|
+
socket.once("connect", () => {
|
|
14
|
+
socket.destroy();
|
|
15
|
+
resolve(true);
|
|
16
|
+
});
|
|
17
|
+
socket.once("error", () => resolve(false));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=is-tcp-port-open.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-tcp-port-open.js","sourceRoot":"","sources":["../../src/net/is-tcp-port-open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAa;IACxD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;YAC3B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TcpSocketConnectOptions } from "@ac-kit/node";
|
|
2
|
+
/**
|
|
3
|
+
* Connects to port, reads the first line, and returns whether it starts with
|
|
4
|
+
* bannerPrefix. Returns false on any connection or read error, or on abort.
|
|
5
|
+
*
|
|
6
|
+
* Note: This function does not support reading multi-line banners. It only
|
|
7
|
+
* reads the first line.
|
|
8
|
+
*
|
|
9
|
+
* @param port The TCP port to connect to.
|
|
10
|
+
* @param bannerPrefix The expected prefix of the banner.
|
|
11
|
+
* @param options Optional parameters including connection options and an abort
|
|
12
|
+
* signal.
|
|
13
|
+
* @returns A promise that resolves to true if the banner starts with the
|
|
14
|
+
* specified prefix, false otherwise.
|
|
15
|
+
*/
|
|
16
|
+
export declare function tryTcpConnectAndReadBanner(port: number, bannerPrefix: string, connectOptions?: TcpSocketConnectOptions): Promise<boolean>;
|
|
17
|
+
//# sourceMappingURL=try-tcp-connect-n-read-banner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"try-tcp-connect-n-read-banner.d.ts","sourceRoot":"","sources":["../../src/net/try-tcp-connect-n-read-banner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAC/C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,uBAAuB,GACtC,OAAO,CAAC,OAAO,CAAC,CAsBlB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { TcpSocket } from "@ac-kit/node";
|
|
3
|
+
/**
|
|
4
|
+
* Connects to port, reads the first line, and returns whether it starts with
|
|
5
|
+
* bannerPrefix. Returns false on any connection or read error, or on abort.
|
|
6
|
+
*
|
|
7
|
+
* Note: This function does not support reading multi-line banners. It only
|
|
8
|
+
* reads the first line.
|
|
9
|
+
*
|
|
10
|
+
* @param port The TCP port to connect to.
|
|
11
|
+
* @param bannerPrefix The expected prefix of the banner.
|
|
12
|
+
* @param options Optional parameters including connection options and an abort
|
|
13
|
+
* signal.
|
|
14
|
+
* @returns A promise that resolves to true if the banner starts with the
|
|
15
|
+
* specified prefix, false otherwise.
|
|
16
|
+
*/
|
|
17
|
+
export async function tryTcpConnectAndReadBanner(port, bannerPrefix, connectOptions) {
|
|
18
|
+
const socket = TcpSocket.from();
|
|
19
|
+
try {
|
|
20
|
+
await socket.connect(port, connectOptions);
|
|
21
|
+
const rl = createInterface({
|
|
22
|
+
input: socket.stream,
|
|
23
|
+
crlfDelay: Infinity,
|
|
24
|
+
signal: connectOptions?.signal,
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
for await (const line of rl) {
|
|
28
|
+
return line.startsWith(bannerPrefix);
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
rl.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
socket.destroy();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=try-tcp-connect-n-read-banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"try-tcp-connect-n-read-banner.js","sourceRoot":"","sources":["../../src/net/try-tcp-connect-n-read-banner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,SAAS,EAA2B,MAAM,cAAc,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,IAAY,EACZ,YAAoB,EACpB,cAAwC;IAExC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,CAAC;QACJ,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QAC3C,MAAM,EAAE,GAAG,eAAe,CAAC;YAC1B,KAAK,EAAE,MAAM,CAAC,MAAM;YACpB,SAAS,EAAE,QAAQ;YACnB,MAAM,EAAE,cAAc,EAAE,MAAM;SAC9B,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACV,EAAE,CAAC,KAAK,EAAE,CAAC;QACZ,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type TlsSocketConnectOptions } from "@ac-kit/node";
|
|
2
|
+
/**
|
|
3
|
+
* Connects to port over TLS, reads the first line, and returns whether it
|
|
4
|
+
* starts with bannerPrefix. Returns false on any connection or read error, or
|
|
5
|
+
* on abort.
|
|
6
|
+
*
|
|
7
|
+
* Use with {@link waitFor} from `@ac-kit/core` to poll until ready:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* await waitFor(() => tryReadTlsBanner(port, banner), {
|
|
11
|
+
* signal: AbortSignal.timeout(30_000),
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function tryTlsConnectAndReadBanner(port: number, bannerPrefix: string, options?: TlsSocketConnectOptions): Promise<boolean>;
|
|
16
|
+
//# sourceMappingURL=try-tls-connect-n-read-banner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"try-tls-connect-n-read-banner.d.ts","sourceRoot":"","sources":["../../src/net/try-tls-connect-n-read-banner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,wBAAsB,0BAA0B,CAC/C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,uBAAuB,GAC/B,OAAO,CAAC,OAAO,CAAC,CAsBlB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { TlsSocket } from "@ac-kit/node";
|
|
3
|
+
/**
|
|
4
|
+
* Connects to port over TLS, reads the first line, and returns whether it
|
|
5
|
+
* starts with bannerPrefix. Returns false on any connection or read error, or
|
|
6
|
+
* on abort.
|
|
7
|
+
*
|
|
8
|
+
* Use with {@link waitFor} from `@ac-kit/core` to poll until ready:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* await waitFor(() => tryReadTlsBanner(port, banner), {
|
|
12
|
+
* signal: AbortSignal.timeout(30_000),
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export async function tryTlsConnectAndReadBanner(port, bannerPrefix, options) {
|
|
17
|
+
let socket;
|
|
18
|
+
try {
|
|
19
|
+
socket = await TlsSocket.connect(port, options);
|
|
20
|
+
const rl = createInterface({
|
|
21
|
+
input: socket.stream,
|
|
22
|
+
crlfDelay: Infinity,
|
|
23
|
+
signal: options?.signal,
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
for await (const line of rl) {
|
|
27
|
+
return line.startsWith(bannerPrefix);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
rl.close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
socket?.destroy();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=try-tls-connect-n-read-banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"try-tls-connect-n-read-banner.js","sourceRoot":"","sources":["../../src/net/try-tls-connect-n-read-banner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAgC,MAAM,cAAc,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,IAAY,EACZ,YAAoB,EACpB,OAAiC;IAEjC,IAAI,MAA6B,CAAC;IAClC,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,eAAe,CAAC;YAC1B,KAAK,EAAE,MAAM,CAAC,MAAM;YACpB,SAAS,EAAE,QAAQ;YACnB,MAAM,EAAE,OAAO,EAAE,MAAM;SACvB,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACV,EAAE,CAAC,KAAK,EAAE,CAAC;QACZ,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACV,MAAM,EAAE,OAAO,EAAE,CAAC;IACnB,CAAC;AACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ac-kit/integration-test-util",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Test helpers that drive and probe external services — Docker containers, TCP and TLS banner checks — from a package's integration tests.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"docker",
|
|
7
|
+
"integration",
|
|
8
|
+
"tcp",
|
|
9
|
+
"testing"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://anthochamp.github.io/node-essentials/api/integration-test-util/",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/anthochamp/node-essentials/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "BSD-3-Clause",
|
|
16
|
+
"author": "Anthony Champagne <dev@anthonychampagne.fr>",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/anthochamp/node-essentials.git",
|
|
20
|
+
"directory": "packages/integration-test-util"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"compile": "tsc -p ./tsconfig.build.json -outDir dist",
|
|
34
|
+
"compile:check": "tsc -noEmit -p ./tsconfig.json",
|
|
35
|
+
"clean": "rimraf dist",
|
|
36
|
+
"build": "yarn clean && yarn compile",
|
|
37
|
+
"test": "yarn run -T vitest run",
|
|
38
|
+
"prepack": "yarn build"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@ac-kit/cmd-docker": "^0.1.0",
|
|
42
|
+
"@ac-kit/core": "^0.1.0",
|
|
43
|
+
"@ac-kit/crypto-random": "^0.1.0",
|
|
44
|
+
"@ac-kit/node": "^0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@ac-kit/tsconfig": "0.1.0",
|
|
48
|
+
"@ac-kit/typedoc-config": "0.1.0",
|
|
49
|
+
"@ac-kit/vitest-config": "0.1.0",
|
|
50
|
+
"@types/node": "26.5.1",
|
|
51
|
+
"@typescript/native": "typescript@7.0.2",
|
|
52
|
+
"rimraf": "6.1.3",
|
|
53
|
+
"typedoc": "0.28.20",
|
|
54
|
+
"typescript": "@typescript/typescript6@6.0.2",
|
|
55
|
+
"vite": "8.3.0",
|
|
56
|
+
"vitest": "5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"vitest": ">=4.0.0"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=24"
|
|
63
|
+
}
|
|
64
|
+
}
|