@harperfast/integration-testing 0.1.0 → 0.3.0-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/README.md +71 -5
- package/dist/harperLifecycle.d.ts +176 -0
- package/dist/harperLifecycle.js +71 -40
- package/dist/harperLifecycle.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loopbackAddressPool.d.ts +50 -0
- package/dist/run.d.ts +2 -0
- package/dist/security/certGenUtils.d.ts +52 -0
- package/dist/security/certGenUtils.js +170 -0
- package/dist/security/certGenUtils.js.map +1 -0
- package/dist/security/crl/generate-test-certs.d.ts +34 -0
- package/dist/security/crl/generate-test-certs.js +81 -0
- package/dist/security/crl/generate-test-certs.js.map +1 -0
- package/dist/security/ocsp/generate-test-certs.d.ts +55 -0
- package/dist/security/ocsp/generate-test-certs.js +106 -0
- package/dist/security/ocsp/generate-test-certs.js.map +1 -0
- package/dist/security/ocspServer.d.ts +18 -0
- package/dist/security/ocspServer.js +132 -0
- package/dist/security/ocspServer.js.map +1 -0
- package/dist/securityServices.d.ts +49 -0
- package/dist/securityServices.js +120 -0
- package/dist/securityServices.js.map +1 -0
- package/dist/src/harperLifecycle.d.ts +156 -0
- package/dist/src/harperLifecycle.js +315 -0
- package/dist/src/harperLifecycle.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/loopbackAddressPool.d.ts +50 -0
- package/dist/src/loopbackAddressPool.js +337 -0
- package/dist/src/loopbackAddressPool.js.map +1 -0
- package/dist/src/run.d.ts +2 -0
- package/dist/src/run.js +94 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/security/certGenUtils.d.ts +52 -0
- package/dist/src/security/certGenUtils.js +170 -0
- package/dist/src/security/certGenUtils.js.map +1 -0
- package/dist/src/security/crl/generate-test-certs.d.ts +34 -0
- package/dist/src/security/crl/generate-test-certs.js +81 -0
- package/dist/src/security/crl/generate-test-certs.js.map +1 -0
- package/dist/src/security/ocsp/generate-test-certs.d.ts +55 -0
- package/dist/src/security/ocsp/generate-test-certs.js +106 -0
- package/dist/src/security/ocsp/generate-test-certs.js.map +1 -0
- package/dist/src/security/ocspServer.d.ts +18 -0
- package/dist/src/security/ocspServer.js +132 -0
- package/dist/src/security/ocspServer.js.map +1 -0
- package/dist/src/securityServices.d.ts +49 -0
- package/dist/src/securityServices.js +120 -0
- package/dist/src/securityServices.js.map +1 -0
- package/dist/src/targz.d.ts +6 -0
- package/dist/src/targz.js +20 -0
- package/dist/src/targz.js.map +1 -0
- package/dist/targz.d.ts +6 -0
- package/package.json +5 -3
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js OCSP (Online Certificate Status Protocol) responder
|
|
3
|
+
*
|
|
4
|
+
* A pure-Node.js replacement for `openssl ocsp`. Handles OCSP requests using pkijs
|
|
5
|
+
* for ASN.1 encoding and certGenUtils for Ed25519 signatures.
|
|
6
|
+
*/
|
|
7
|
+
import { createServer } from 'node:http';
|
|
8
|
+
import * as pkijs from 'pkijs';
|
|
9
|
+
import * as asn1js from 'asn1js';
|
|
10
|
+
import { signBasicOCSPResponse } from "./certGenUtils.js";
|
|
11
|
+
const OCSP_BASIC_RESPONSE_OID = '1.3.6.1.5.5.7.48.1.1';
|
|
12
|
+
/**
|
|
13
|
+
* Build a DER-encoded OCSPResponse for the given DER-encoded OCSPRequest.
|
|
14
|
+
*/
|
|
15
|
+
async function buildOcspResponse(requestBody, certs) {
|
|
16
|
+
// Parse incoming OCSP request
|
|
17
|
+
const asn1 = asn1js.fromBER(requestBody);
|
|
18
|
+
if (asn1.offset === -1) {
|
|
19
|
+
throw new Error('Failed to parse OCSP request');
|
|
20
|
+
}
|
|
21
|
+
const ocspRequest = new pkijs.OCSPRequest({ schema: asn1.result });
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const nextUpdate = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour
|
|
24
|
+
// Build SingleResponse for each requested certificate
|
|
25
|
+
const singleResponses = ocspRequest.tbsRequest.requestList.map((req) => {
|
|
26
|
+
// Extract the decimal serial number to look up status
|
|
27
|
+
const serialDec = req.reqCert.serialNumber.valueBlock.valueDec;
|
|
28
|
+
const status = certs.statusMap.get(String(serialDec));
|
|
29
|
+
// certStatus ASN.1 encoding:
|
|
30
|
+
// good [0] IMPLICIT NULL
|
|
31
|
+
// revoked [1] IMPLICIT RevokedInfo (SEQUENCE with revocation time)
|
|
32
|
+
// unknown [2] IMPLICIT UnknownInfo
|
|
33
|
+
let certStatus;
|
|
34
|
+
if (status === 'good') {
|
|
35
|
+
certStatus = new asn1js.Primitive({
|
|
36
|
+
idBlock: { tagClass: 3, tagNumber: 0 },
|
|
37
|
+
valueHex: new ArrayBuffer(0),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else if (status === 'revoked') {
|
|
41
|
+
certStatus = new asn1js.Constructed({
|
|
42
|
+
idBlock: { tagClass: 3, tagNumber: 1 },
|
|
43
|
+
value: [new asn1js.GeneralizedTime({ valueDate: now })],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// unknown
|
|
48
|
+
certStatus = new asn1js.Primitive({
|
|
49
|
+
idBlock: { tagClass: 3, tagNumber: 2 },
|
|
50
|
+
valueHex: new ArrayBuffer(0),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return new pkijs.SingleResponse({
|
|
54
|
+
certID: req.reqCert, // echo back the same certID
|
|
55
|
+
certStatus,
|
|
56
|
+
thisUpdate: now,
|
|
57
|
+
nextUpdate,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
// Use the OCSP responder cert's subject as the responderID (byName, tagNumber=1)
|
|
61
|
+
const responseData = new pkijs.ResponseData({
|
|
62
|
+
responderID: certs.ocspCert.subject,
|
|
63
|
+
producedAt: now,
|
|
64
|
+
responses: singleResponses,
|
|
65
|
+
});
|
|
66
|
+
// Create BasicOCSPResponse and sign it
|
|
67
|
+
const basicResponse = new pkijs.BasicOCSPResponse({
|
|
68
|
+
tbsResponseData: responseData,
|
|
69
|
+
certs: [certs.ocspCert, certs.caCert],
|
|
70
|
+
});
|
|
71
|
+
await signBasicOCSPResponse(basicResponse, certs.ocspKeyPair.privateKey);
|
|
72
|
+
// Encode BasicOCSPResponse to DER, wrap in OCSPResponse
|
|
73
|
+
const basicDer = basicResponse.toSchema().toBER();
|
|
74
|
+
const ocspResponse = new pkijs.OCSPResponse({
|
|
75
|
+
responseStatus: new asn1js.Enumerated({ value: 0 }), // successful
|
|
76
|
+
responseBytes: new pkijs.ResponseBytes({
|
|
77
|
+
responseType: OCSP_BASIC_RESPONSE_OID,
|
|
78
|
+
response: new asn1js.OctetString({ valueHex: basicDer }),
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
return Buffer.from(ocspResponse.toSchema().toBER());
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Start a Node.js HTTP OCSP responder.
|
|
85
|
+
*
|
|
86
|
+
* @param port - Port to listen on
|
|
87
|
+
* @param certs - In-memory certificate and key data for the responder
|
|
88
|
+
* @returns Promise resolving to the HTTP server (already listening)
|
|
89
|
+
*/
|
|
90
|
+
export async function startOcspServer(port, certs) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const server = createServer((req, res) => {
|
|
93
|
+
if (req.method !== 'POST' && req.method !== 'GET') {
|
|
94
|
+
res.writeHead(405);
|
|
95
|
+
res.end();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const chunks = [];
|
|
99
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
100
|
+
req.on('end', async () => {
|
|
101
|
+
try {
|
|
102
|
+
const body = Buffer.concat(chunks);
|
|
103
|
+
const response = await buildOcspResponse(body, certs);
|
|
104
|
+
res.writeHead(200, { 'Content-Type': 'application/ocsp-response' });
|
|
105
|
+
res.end(response);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.error('OCSP server error:', err);
|
|
109
|
+
// Return an "internalError" response
|
|
110
|
+
const errResponse = new pkijs.OCSPResponse({
|
|
111
|
+
responseStatus: new asn1js.Enumerated({ value: 2 }), // internalError
|
|
112
|
+
});
|
|
113
|
+
const errDer = Buffer.from(errResponse.toSchema().toBER());
|
|
114
|
+
res.writeHead(200, { 'Content-Type': 'application/ocsp-response' });
|
|
115
|
+
res.end(errDer);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
req.on('error', (err) => {
|
|
119
|
+
console.error('OCSP request error:', err);
|
|
120
|
+
res.writeHead(500);
|
|
121
|
+
res.end();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
server.on('error', reject);
|
|
125
|
+
server.listen(port, '127.0.0.1', () => resolve(server));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** Stop a running OCSP server */
|
|
129
|
+
export async function stopOcspServer(server) {
|
|
130
|
+
return new Promise((resolve) => server.close(() => resolve()));
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=ocspServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocspServer.js","sourceRoot":"","sources":["../../src/security/ocspServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Services Utilities for Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for managing OCSP responders, CRL servers,
|
|
5
|
+
* and certificate generation for security-related integration tests.
|
|
6
|
+
*
|
|
7
|
+
* All certificate generation and OCSP responding is done in pure Node.js
|
|
8
|
+
* using webcrypto + pkijs — no openssl CLI dependency.
|
|
9
|
+
*/
|
|
10
|
+
import { type Server } from 'node:http';
|
|
11
|
+
import type { OcspCertificates } from './security/ocsp/generate-test-certs.ts';
|
|
12
|
+
import type { CrlCertificates } from './security/crl/generate-test-certs.ts';
|
|
13
|
+
export interface OcspResponderContext {
|
|
14
|
+
server: Server;
|
|
15
|
+
port: number;
|
|
16
|
+
certsPath: string;
|
|
17
|
+
certs: OcspCertificates;
|
|
18
|
+
}
|
|
19
|
+
export interface CrlServerContext {
|
|
20
|
+
server: Server;
|
|
21
|
+
port: number;
|
|
22
|
+
certsPath: string;
|
|
23
|
+
certs: CrlCertificates;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Stop an OCSP responder server
|
|
27
|
+
*/
|
|
28
|
+
export declare function stopOcspResponder(ctx: OcspResponderContext): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Start a Node.js HTTP server to serve CRL files
|
|
31
|
+
*/
|
|
32
|
+
export declare function startCrlServer(certsPath: string, port: number, certs: CrlCertificates): Promise<CrlServerContext>;
|
|
33
|
+
/**
|
|
34
|
+
* Stop a CRL server
|
|
35
|
+
*/
|
|
36
|
+
export declare function stopCrlServer(ctx: CrlServerContext): Promise<void>;
|
|
37
|
+
export { generateOcspCertificates, type OcspCertificates } from './security/ocsp/generate-test-certs.ts';
|
|
38
|
+
export { generateCrlCertificates, type CrlCertificates } from './security/crl/generate-test-certs.ts';
|
|
39
|
+
/**
|
|
40
|
+
* Setup CRL server with automatic port allocation and retry on conflict
|
|
41
|
+
*/
|
|
42
|
+
export declare function setupCrlServerWithCerts(certsPath: string, hostname?: string, maxRetries?: number): Promise<CrlServerContext>;
|
|
43
|
+
/**
|
|
44
|
+
* Setup OCSP responder with automatic port allocation and retry on conflict
|
|
45
|
+
*
|
|
46
|
+
* Generates certificates with OCSP URL embedded, then starts the Node.js
|
|
47
|
+
* OCSP responder on the same port.
|
|
48
|
+
*/
|
|
49
|
+
export declare function setupOcspResponderWithCerts(certsPath: string, hostname?: string, maxRetries?: number): Promise<OcspResponderContext>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Services Utilities for Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for managing OCSP responders, CRL servers,
|
|
5
|
+
* and certificate generation for security-related integration tests.
|
|
6
|
+
*
|
|
7
|
+
* All certificate generation and OCSP responding is done in pure Node.js
|
|
8
|
+
* using webcrypto + pkijs — no openssl CLI dependency.
|
|
9
|
+
*/
|
|
10
|
+
import { createServer } from 'node:http';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { startOcspServer, stopOcspServer } from "./security/ocspServer.js";
|
|
14
|
+
/**
|
|
15
|
+
* Stop an OCSP responder server
|
|
16
|
+
*/
|
|
17
|
+
export async function stopOcspResponder(ctx) {
|
|
18
|
+
return stopOcspServer(ctx.server);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Start a Node.js HTTP server to serve CRL files
|
|
22
|
+
*/
|
|
23
|
+
export async function startCrlServer(certsPath, port, certs) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const server = createServer((req, res) => {
|
|
26
|
+
if (req.url === '/test.crl') {
|
|
27
|
+
try {
|
|
28
|
+
const crl = readFileSync(join(certsPath, 'test.crl'));
|
|
29
|
+
res.writeHead(200, {
|
|
30
|
+
'Content-Type': 'application/pkix-crl',
|
|
31
|
+
'Cache-Control': 'no-cache',
|
|
32
|
+
});
|
|
33
|
+
res.end(crl);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
res.writeHead(500);
|
|
37
|
+
res.end('Internal Server Error');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (req.url === '/health') {
|
|
41
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
42
|
+
res.end('OK');
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
res.writeHead(404);
|
|
46
|
+
res.end('Not Found');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
server.on('error', reject);
|
|
50
|
+
server.listen(port, () => {
|
|
51
|
+
const address = server.address();
|
|
52
|
+
if (!address || typeof address === 'string') {
|
|
53
|
+
reject(new Error('Server did not bind to a network port'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
resolve({
|
|
57
|
+
server,
|
|
58
|
+
port: address.port,
|
|
59
|
+
certsPath,
|
|
60
|
+
certs,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Stop a CRL server
|
|
67
|
+
*/
|
|
68
|
+
export async function stopCrlServer(ctx) {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
ctx.server.close(() => resolve());
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Re-export certificate generation functions and types
|
|
74
|
+
export { generateOcspCertificates } from "./security/ocsp/generate-test-certs.js";
|
|
75
|
+
export { generateCrlCertificates } from "./security/crl/generate-test-certs.js";
|
|
76
|
+
/**
|
|
77
|
+
* Setup CRL server with automatic port allocation and retry on conflict
|
|
78
|
+
*/
|
|
79
|
+
export async function setupCrlServerWithCerts(certsPath, hostname = '127.0.0.1', maxRetries = 5) {
|
|
80
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
81
|
+
const port = 50000 + Math.floor(Math.random() * 10000);
|
|
82
|
+
try {
|
|
83
|
+
const { generateCrlCertificates } = await import("./security/crl/generate-test-certs.js");
|
|
84
|
+
const certs = await generateCrlCertificates(certsPath, hostname, port);
|
|
85
|
+
return startCrlServer(certsPath, port, certs);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error.code === 'EADDRINUSE' && attempt < maxRetries - 1) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Failed to setup CRL server after ${attempt + 1} attempts: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`Failed to setup CRL server after ${maxRetries} attempts`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Setup OCSP responder with automatic port allocation and retry on conflict
|
|
98
|
+
*
|
|
99
|
+
* Generates certificates with OCSP URL embedded, then starts the Node.js
|
|
100
|
+
* OCSP responder on the same port.
|
|
101
|
+
*/
|
|
102
|
+
export async function setupOcspResponderWithCerts(certsPath, hostname = '127.0.0.1', maxRetries = 5) {
|
|
103
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
104
|
+
const port = 50000 + Math.floor(Math.random() * 10000);
|
|
105
|
+
try {
|
|
106
|
+
const { generateOcspCertificates } = await import("./security/ocsp/generate-test-certs.js");
|
|
107
|
+
const { files, serverCerts } = await generateOcspCertificates(certsPath, hostname, port);
|
|
108
|
+
const server = await startOcspServer(port, serverCerts);
|
|
109
|
+
return { server, port, certsPath, certs: files };
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error.code === 'EADDRINUSE' && attempt < maxRetries - 1) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Failed to setup OCSP responder after ${attempt + 1} attempts: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`Failed to setup OCSP responder after ${maxRetries} attempts`);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=securityServices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"securityServices.js","sourceRoot":"","sources":["../src/securityServices.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { ChildProcess } from 'node:child_process';
|
|
2
|
+
import { type SuiteContext, type TestContext } from 'node:test';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal context interface required by startHarper/teardownHarper.
|
|
5
|
+
*
|
|
6
|
+
* This is intentionally loose so it can be satisfied by:
|
|
7
|
+
* - node:test SuiteContext/TestContext objects (via ContextWithHarper)
|
|
8
|
+
* - Plain objects (e.g. Playwright worker fixtures: `createHarperContext()`)
|
|
9
|
+
*/
|
|
10
|
+
export interface HarperTestContext {
|
|
11
|
+
/** Optional name used for log directory naming (e.g. suite name or Playwright worker index). */
|
|
12
|
+
name?: string;
|
|
13
|
+
/** Populated by startHarper(). May be pre-seeded with dataRootDir/hostname to reuse across restarts. */
|
|
14
|
+
harper?: Partial<HarperContext>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A started context — harper is fully populated after startHarper() resolves.
|
|
18
|
+
*/
|
|
19
|
+
export interface StartedHarperTestContext extends HarperTestContext {
|
|
20
|
+
harper: HarperContext;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Creates a plain object satisfying HarperTestContext, for use outside node:test
|
|
24
|
+
* (e.g. as a Playwright worker fixture).
|
|
25
|
+
*
|
|
26
|
+
* @param name Optional name for log directory naming (e.g. Playwright worker index).
|
|
27
|
+
*/
|
|
28
|
+
export declare function createHarperContext(name?: string): HarperTestContext;
|
|
29
|
+
export declare const OPERATIONS_API_PORT = 9925;
|
|
30
|
+
export declare const DEFAULT_ADMIN_USERNAME = "admin";
|
|
31
|
+
export declare const DEFAULT_ADMIN_PASSWORD = "Abc1234!";
|
|
32
|
+
export declare const DEFAULT_STARTUP_TIMEOUT_MS: number;
|
|
33
|
+
/**
|
|
34
|
+
* Options for setting up a Harper instance.
|
|
35
|
+
*/
|
|
36
|
+
export interface StartHarperOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Timeout in milliseconds to wait for Harper to start.
|
|
39
|
+
* @default 30000
|
|
40
|
+
*/
|
|
41
|
+
startupTimeoutMs?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Additional configuration options to pass to the Harper CLI.
|
|
44
|
+
*/
|
|
45
|
+
config?: any;
|
|
46
|
+
/**
|
|
47
|
+
* Environment variables to set when running Harper.
|
|
48
|
+
*/
|
|
49
|
+
env?: any;
|
|
50
|
+
/**
|
|
51
|
+
* Explicit path to the Harper CLI script (dist/bin/harper.js).
|
|
52
|
+
* If not provided, resolution order is:
|
|
53
|
+
* 1. This option
|
|
54
|
+
* 2. HARPER_INTEGRATION_TEST_INSTALL_SCRIPT environment variable
|
|
55
|
+
* 3. Auto-resolved from 'harper' package in node_modules
|
|
56
|
+
*/
|
|
57
|
+
harperBinPath?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface HarperContext {
|
|
60
|
+
/** Absolute path to the Harper installation directory */
|
|
61
|
+
dataRootDir: string;
|
|
62
|
+
/** Admin credentials for the Harper instance */
|
|
63
|
+
admin: {
|
|
64
|
+
/** Admin username (default: 'admin') */
|
|
65
|
+
username: string;
|
|
66
|
+
/** Admin password (default: 'Abc1234!') */
|
|
67
|
+
password: string;
|
|
68
|
+
};
|
|
69
|
+
/** HTTP URL for the Harper instance (e.g., 'http://127.0.0.2:9926') */
|
|
70
|
+
httpURL: string;
|
|
71
|
+
/** Operations API URL (e.g., 'http://127.0.0.2:9925') */
|
|
72
|
+
operationsAPIURL: string;
|
|
73
|
+
/** Assigned loopback IP address (e.g., '127.0.0.2') */
|
|
74
|
+
hostname: string;
|
|
75
|
+
/** Child process for the Harper instance */
|
|
76
|
+
process: ChildProcess;
|
|
77
|
+
/** Absolute path to the log directory for this suite (only set when HARPER_INTEGRATION_TEST_LOG_DIR is configured) */
|
|
78
|
+
logDir?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Test context interface with Harper instance details, for use with node:test.
|
|
82
|
+
*
|
|
83
|
+
* This interface is populated by `startHarper()` and contains
|
|
84
|
+
* all necessary information to interact with the test Harper instance.
|
|
85
|
+
*
|
|
86
|
+
* For use outside node:test (e.g. Playwright), use `createHarperContext()` to
|
|
87
|
+
* create a plain object satisfying `HarperTestContext` instead.
|
|
88
|
+
*/
|
|
89
|
+
export interface ContextWithHarper extends SuiteContext, TestContext {
|
|
90
|
+
harper: HarperContext;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Sets up a Harper instance with a component pre-installed from a local directory.
|
|
94
|
+
*
|
|
95
|
+
* Copies `fixturePath` into `{dataRootDir}/components/{name}` before Harper starts,
|
|
96
|
+
* so the component is available on the first request without a post-startup deploy.
|
|
97
|
+
* Use this when tests need a known route available at startup (e.g. mTLS cert tests).
|
|
98
|
+
*
|
|
99
|
+
* @param ctx - The test context to populate with Harper instance details
|
|
100
|
+
* @param fixturePath - Absolute path to the component directory to pre-install
|
|
101
|
+
* @param options - Optional configuration for the setup process
|
|
102
|
+
*/
|
|
103
|
+
export declare function setupHarperWithFixture(ctx: HarperTestContext, fixturePath: string, options?: StartHarperOptions): Promise<StartedHarperTestContext>;
|
|
104
|
+
/**
|
|
105
|
+
* Sets up and starts a Harper instance for testing.
|
|
106
|
+
*
|
|
107
|
+
* @param ctx - The test context to populate with Harper instance details
|
|
108
|
+
* @param options - Optional configuration for the setup process
|
|
109
|
+
* @returns The context with the `harper` property populated
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* suite('My tests', (ctx: ContextWithHarper) => {
|
|
114
|
+
* before(async () => {
|
|
115
|
+
* await startHarper(ctx);
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* after(async () => {
|
|
119
|
+
* await teardownHarper(ctx);
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* test('can connect', async () => {
|
|
123
|
+
* const response = await fetch(ctx.harper.httpURL);
|
|
124
|
+
* // ...
|
|
125
|
+
* });
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function startHarper(ctx: HarperTestContext, options?: StartHarperOptions): Promise<StartedHarperTestContext>;
|
|
130
|
+
/**
|
|
131
|
+
* Kill harper process (can be used for teardown, or killing it before a restart)
|
|
132
|
+
* @param ctx
|
|
133
|
+
*/
|
|
134
|
+
export declare function killHarper(ctx: StartedHarperTestContext): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Tears down a Harper instance and cleans up all resources.
|
|
137
|
+
*
|
|
138
|
+
* This function stops the Harper instance, releases the loopback address,
|
|
139
|
+
* and removes the installation directory.
|
|
140
|
+
* @param ctx - The test context with Harper instance details
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* suite('My tests', (ctx: ContextWithHarper) => {
|
|
145
|
+
* before(async () => {
|
|
146
|
+
* await startHarper(ctx);
|
|
147
|
+
* });
|
|
148
|
+
*
|
|
149
|
+
* after(async () => {
|
|
150
|
+
* await teardownHarper(ctx);
|
|
151
|
+
* });
|
|
152
|
+
* });
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export declare function teardownHarper(ctx: StartedHarperTestContext): Promise<void>;
|
|
156
|
+
export declare function sendOperation(context: HarperContext, operation: any): Promise<any>;
|