portok 1.0.0 → 1.0.1
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/Dockerfile +1 -1
- package/bench/{baseline.bench.mjs → baseline.bench.js} +5 -3
- package/bench/{connections.bench.mjs → connections.bench.js} +5 -3
- package/bench/{keepalive.bench.mjs → keepalive.bench.js} +15 -16
- package/bench/{latency.bench.mjs → latency.bench.js} +5 -3
- package/bench/{run.mjs → run.js} +11 -11
- package/bench/{switching.bench.mjs → switching.bench.js} +5 -3
- package/bench/{throughput.bench.mjs → throughput.bench.js} +5 -3
- package/bench/{validate.mjs → validate.js} +7 -7
- package/package.json +10 -12
- package/{portok.mjs → portok.js} +5 -4
- package/{portokd.mjs → portokd.js} +6 -5
- package/test/{cli.test.mjs → cli.test.js} +7 -6
- package/test/{drain.test.mjs → drain.test.js} +7 -7
- package/test/helpers/{mock-server.mjs → mock-server.js} +19 -11
- package/test/{metrics.test.mjs → metrics.test.js} +6 -6
- package/test/{proxy.test.mjs → proxy.test.js} +8 -7
- package/test/{rollback.test.mjs → rollback.test.js} +8 -8
- package/test/{security.test.mjs → security.test.js} +6 -6
- package/test/{switching.test.mjs → switching.test.js} +5 -5
package/Dockerfile
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Compares direct server performance vs proxied performance
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
const shortDuration = Math.max(2, Math.floor(duration / 2));
|
|
11
11
|
|
|
12
12
|
// Setup mock server for direct test
|
|
@@ -71,3 +71,5 @@ export async function run({ duration, adminToken }) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
module.exports = { run };
|
|
75
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests scaling with increasing concurrent connections
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup
|
|
11
11
|
const mockServer = await createMockServer();
|
|
12
12
|
const proxyPort = await getFreePort();
|
|
@@ -68,3 +68,5 @@ export async function run({ duration, adminToken }) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
module.exports = { run };
|
|
72
|
+
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* - Keep-alive should be at least 50% faster than no-keepalive
|
|
7
7
|
* - Proxied RPS should be at least 20% of direct RPS
|
|
8
8
|
*
|
|
9
|
-
* Can be run standalone or via bench/run.
|
|
10
|
-
* node bench/keepalive.bench.
|
|
9
|
+
* Can be run standalone or via bench/run.js:
|
|
10
|
+
* node bench/keepalive.bench.js --quick
|
|
11
11
|
* npm run bench
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const autocannon = require('autocannon');
|
|
15
|
+
const { spawn } = require('node:child_process');
|
|
16
|
+
const http = require('node:http');
|
|
17
17
|
|
|
18
18
|
// =============================================================================
|
|
19
19
|
// Utilities (used when running standalone)
|
|
@@ -62,7 +62,7 @@ async function createMockServer(port = 0) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async function startDaemonWithKA(listenPort, targetPort, adminToken, keepAlive = true) {
|
|
65
|
-
const proc = spawn('node', ['portokd.
|
|
65
|
+
const proc = spawn('node', ['portokd.js'], {
|
|
66
66
|
env: {
|
|
67
67
|
...process.env,
|
|
68
68
|
LISTEN_PORT: String(listenPort),
|
|
@@ -99,10 +99,10 @@ async function startDaemonWithKA(listenPort, targetPort, adminToken, keepAlive =
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// =============================================================================
|
|
102
|
-
// Main run function (called by bench/run.
|
|
102
|
+
// Main run function (called by bench/run.js)
|
|
103
103
|
// =============================================================================
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
async function run({ duration, adminToken }) {
|
|
106
106
|
const shortDuration = Math.max(2, Math.floor(duration / 2));
|
|
107
107
|
const CONNECTIONS = 100;
|
|
108
108
|
const PIPELINING = 10;
|
|
@@ -187,9 +187,7 @@ export async function run({ duration, adminToken }) {
|
|
|
187
187
|
// Standalone execution
|
|
188
188
|
// =============================================================================
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (isMain) {
|
|
190
|
+
if (require.main === module) {
|
|
193
191
|
const args = process.argv.slice(2);
|
|
194
192
|
const isQuick = args.includes('--quick');
|
|
195
193
|
const isJson = args.includes('--json');
|
|
@@ -209,9 +207,7 @@ if (isMain) {
|
|
|
209
207
|
log('╚══════════════════════════════════════════════════════════════╝');
|
|
210
208
|
log('');
|
|
211
209
|
|
|
212
|
-
|
|
213
|
-
const results = await run({ duration, adminToken });
|
|
214
|
-
|
|
210
|
+
run({ duration, adminToken }).then(results => {
|
|
215
211
|
// Validation
|
|
216
212
|
const rpsPass = results.proxiedKeepAlive.rps >= results.direct.rps * 0.20;
|
|
217
213
|
const kaEffective = results.proxiedKeepAlive.rps >= results.proxiedNoKeepAlive.rps * 1.5;
|
|
@@ -241,8 +237,11 @@ if (isMain) {
|
|
|
241
237
|
}
|
|
242
238
|
|
|
243
239
|
process.exit(0);
|
|
244
|
-
}
|
|
240
|
+
}).catch(err => {
|
|
245
241
|
console.error('Benchmark failed:', err);
|
|
246
242
|
process.exit(1);
|
|
247
|
-
}
|
|
243
|
+
});
|
|
248
244
|
}
|
|
245
|
+
|
|
246
|
+
module.exports = { run };
|
|
247
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Measures latency distribution under moderate load
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup
|
|
11
11
|
const mockServer = await createMockServer();
|
|
12
12
|
const proxyPort = await getFreePort();
|
|
@@ -45,3 +45,5 @@ export async function run({ duration, adminToken }) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
module.exports = { run };
|
|
49
|
+
|
package/bench/{run.mjs → run.js}
RENAMED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Orchestrates all benchmarks and outputs formatted results
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const http = require('node:http');
|
|
10
10
|
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Configuration
|
|
@@ -82,7 +82,7 @@ async function createMockServer(port = 0) {
|
|
|
82
82
|
// =============================================================================
|
|
83
83
|
|
|
84
84
|
async function startDaemon(listenPort, targetPort) {
|
|
85
|
-
const proc = spawn('node', ['portokd.
|
|
85
|
+
const proc = spawn('node', ['portokd.js'], {
|
|
86
86
|
env: {
|
|
87
87
|
...process.env,
|
|
88
88
|
LISTEN_PORT: String(listenPort),
|
|
@@ -116,7 +116,7 @@ async function startDaemon(listenPort, targetPort) {
|
|
|
116
116
|
// =============================================================================
|
|
117
117
|
|
|
118
118
|
async function runBenchmark(file) {
|
|
119
|
-
const module =
|
|
119
|
+
const module = require(file);
|
|
120
120
|
return module.run({ duration, adminToken: ADMIN_TOKEN });
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -165,12 +165,12 @@ async function main() {
|
|
|
165
165
|
|
|
166
166
|
// Run each benchmark
|
|
167
167
|
const benchmarks = [
|
|
168
|
-
{ name: 'Throughput', file: './throughput.bench.
|
|
169
|
-
{ name: 'Latency', file: './latency.bench.
|
|
170
|
-
{ name: 'Connections', file: './connections.bench.
|
|
171
|
-
{ name: 'Switching', file: './switching.bench.
|
|
172
|
-
{ name: 'Baseline', file: './baseline.bench.
|
|
173
|
-
{ name: 'KeepAlive', file: './keepalive.bench.
|
|
168
|
+
{ name: 'Throughput', file: './throughput.bench.js' },
|
|
169
|
+
{ name: 'Latency', file: './latency.bench.js' },
|
|
170
|
+
{ name: 'Connections', file: './connections.bench.js' },
|
|
171
|
+
{ name: 'Switching', file: './switching.bench.js' },
|
|
172
|
+
{ name: 'Baseline', file: './baseline.bench.js' },
|
|
173
|
+
{ name: 'KeepAlive', file: './keepalive.bench.js' },
|
|
174
174
|
];
|
|
175
175
|
|
|
176
176
|
for (const bench of benchmarks) {
|
|
@@ -207,5 +207,5 @@ main().catch(err => {
|
|
|
207
207
|
process.exit(1);
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
module.exports = { createMockServer, startDaemon, getFreePort, waitFor, formatNumber, ADMIN_TOKEN };
|
|
211
211
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Measures switch latency and request loss during switch
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber, ADMIN_TOKEN } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup two servers
|
|
11
11
|
const mockServer1 = await createMockServer();
|
|
12
12
|
const mockServer2 = await createMockServer();
|
|
@@ -94,3 +94,5 @@ export async function run({ duration, adminToken }) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
module.exports = { run };
|
|
98
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Measures maximum requests per second with high concurrency
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const autocannon = require('autocannon');
|
|
7
|
+
const { createMockServer, startDaemon, getFreePort, formatNumber } = require('./run.js');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
async function run({ duration, adminToken }) {
|
|
10
10
|
// Setup
|
|
11
11
|
const mockServer = await createMockServer();
|
|
12
12
|
const proxyPort = await getFreePort();
|
|
@@ -42,3 +42,5 @@ export async function run({ duration, adminToken }) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
module.exports = { run };
|
|
46
|
+
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* - p99 <= 50ms
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
|
-
* node bench/validate.
|
|
13
|
-
* node bench/validate.
|
|
14
|
-
* FAST_PATH=1 node bench/validate.
|
|
12
|
+
* node bench/validate.js
|
|
13
|
+
* node bench/validate.js --quick
|
|
14
|
+
* FAST_PATH=1 node bench/validate.js
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const autocannon = require('autocannon');
|
|
18
|
+
const { spawn } = require('node:child_process');
|
|
19
|
+
const http = require('node:http');
|
|
20
20
|
|
|
21
21
|
const DURATION = parseInt(process.env.BENCH_DURATION || '10', 10);
|
|
22
22
|
const CONNECTIONS = parseInt(process.env.BENCH_CONNECTIONS || '50', 10);
|
|
@@ -72,7 +72,7 @@ async function createMockServer() {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
async function startProxy(listenPort, targetPort) {
|
|
75
|
-
const proc = spawn('node', ['portokd.
|
|
75
|
+
const proc = spawn('node', ['portokd.js'], {
|
|
76
76
|
env: {
|
|
77
77
|
...process.env,
|
|
78
78
|
LISTEN_PORT: String(listenPort),
|
package/package.json
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portok",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Zero-downtime deployment proxy - routes traffic through a stable port to internal app instances with health-gated switching",
|
|
5
|
-
"
|
|
6
|
-
"main": "portokd.mjs",
|
|
5
|
+
"main": "portokd.js",
|
|
7
6
|
"bin": {
|
|
8
|
-
"portok": "./portok.
|
|
9
|
-
"portokd": "./portokd.
|
|
7
|
+
"portok": "./portok.js",
|
|
8
|
+
"portokd": "./portokd.js"
|
|
10
9
|
},
|
|
11
10
|
"scripts": {
|
|
12
|
-
"start": "node portokd.
|
|
13
|
-
"test": "node --test --test-timeout=120000 test/*.test.
|
|
14
|
-
"test:watch": "node --test --watch test/*.test.
|
|
15
|
-
"bench": "node bench/run.
|
|
16
|
-
"bench:quick": "node bench/run.
|
|
17
|
-
"bench:json": "node bench/run.
|
|
11
|
+
"start": "node portokd.js",
|
|
12
|
+
"test": "node --test --test-timeout=120000 test/*.test.js",
|
|
13
|
+
"test:watch": "node --test --watch test/*.test.js",
|
|
14
|
+
"bench": "node bench/run.js",
|
|
15
|
+
"bench:quick": "node bench/run.js --quick",
|
|
16
|
+
"bench:json": "node bench/run.js --json"
|
|
18
17
|
},
|
|
19
18
|
"keywords": [
|
|
20
19
|
"proxy",
|
|
@@ -36,4 +35,3 @@
|
|
|
36
35
|
"node": ">=20.0.0"
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
|
-
|
package/{portok.mjs → portok.js}
RENAMED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* Communicates with the daemon via HTTP to query status, metrics, and trigger switches.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
|
|
8
12
|
// =============================================================================
|
|
9
13
|
// ANSI Colors (only if TTY)
|
|
10
14
|
// =============================================================================
|
|
@@ -61,9 +65,6 @@ function parseArgs(args) {
|
|
|
61
65
|
// Env File Parser (for --instance support)
|
|
62
66
|
// =============================================================================
|
|
63
67
|
|
|
64
|
-
import fs from 'node:fs';
|
|
65
|
-
import path from 'node:path';
|
|
66
|
-
|
|
67
68
|
const ENV_FILE_DIR = '/etc/portok';
|
|
68
69
|
|
|
69
70
|
/**
|
|
@@ -384,7 +385,7 @@ async function cmdInit(options) {
|
|
|
384
385
|
function generateToken(length = 32) {
|
|
385
386
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
386
387
|
let token = '';
|
|
387
|
-
const randomBytes =
|
|
388
|
+
const randomBytes = crypto.randomBytes(length);
|
|
388
389
|
for (let i = 0; i < length; i++) {
|
|
389
390
|
token += chars[randomBytes[i] % chars.length];
|
|
390
391
|
}
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* - Connection header stripping for proper keep-alive upstream
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const http = require('http');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const crypto = require('crypto');
|
|
19
|
+
const httpProxy = require('http-proxy');
|
|
20
20
|
|
|
21
21
|
// =============================================================================
|
|
22
22
|
// Configuration
|
|
@@ -779,7 +779,7 @@ process.on('SIGINT', () => {
|
|
|
779
779
|
});
|
|
780
780
|
|
|
781
781
|
// Export for testing
|
|
782
|
-
|
|
782
|
+
module.exports = {
|
|
783
783
|
config,
|
|
784
784
|
state,
|
|
785
785
|
metrics,
|
|
@@ -791,3 +791,4 @@ export {
|
|
|
791
791
|
server,
|
|
792
792
|
upstreamAgent,
|
|
793
793
|
};
|
|
794
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests the portok CLI commands
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn, execSync } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('CLI', () => {
|
|
12
12
|
let mockServer;
|
|
@@ -18,7 +18,7 @@ describe('CLI', () => {
|
|
|
18
18
|
mockServer = await createMockServer({ port: 0 });
|
|
19
19
|
proxyPort = await getFreePort();
|
|
20
20
|
|
|
21
|
-
daemonProcess = spawn('node', ['portokd.
|
|
21
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
22
22
|
env: {
|
|
23
23
|
...process.env,
|
|
24
24
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -64,7 +64,7 @@ describe('CLI', () => {
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
try {
|
|
67
|
-
const output = execSync(`node portok.
|
|
67
|
+
const output = execSync(`node portok.js ${args}`, {
|
|
68
68
|
encoding: 'utf-8',
|
|
69
69
|
env,
|
|
70
70
|
timeout: 15000,
|
|
@@ -218,3 +218,4 @@ describe('CLI', () => {
|
|
|
218
218
|
});
|
|
219
219
|
});
|
|
220
220
|
});
|
|
221
|
+
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Tests that existing connections drain to old port while new connections go to new port
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const http = require('node:http');
|
|
9
|
+
const { spawn } = require('node:child_process');
|
|
10
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
11
11
|
|
|
12
12
|
describe('Connection Draining', () => {
|
|
13
13
|
let mockServer1;
|
|
@@ -24,7 +24,7 @@ describe('Connection Draining', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start daemon with longer drain time for testing
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -148,7 +148,7 @@ describe('Connection Draining', () => {
|
|
|
148
148
|
it('should clear drainUntil after drain period', async () => {
|
|
149
149
|
// Use a fresh daemon with shorter drain for this test
|
|
150
150
|
const shortDrainPort = await getFreePort();
|
|
151
|
-
const shortDaemon = spawn('node', ['portokd.
|
|
151
|
+
const shortDaemon = spawn('node', ['portokd.js'], {
|
|
152
152
|
env: {
|
|
153
153
|
...process.env,
|
|
154
154
|
LISTEN_PORT: String(shortDrainPort),
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
* Provides configurable responses, delays, and health status
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const http = require('http');
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
let WebSocketServer;
|
|
8
|
+
// Try to load ws, handle when not available
|
|
9
|
+
let WebSocketServer = null;
|
|
10
10
|
try {
|
|
11
|
-
const ws =
|
|
11
|
+
const ws = require('ws');
|
|
12
12
|
WebSocketServer = ws.WebSocketServer;
|
|
13
13
|
} catch {
|
|
14
|
-
|
|
14
|
+
// ws not available
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -24,7 +24,7 @@ try {
|
|
|
24
24
|
* @param {boolean} options.enableWebSocket - Enable WebSocket support (default: true)
|
|
25
25
|
* @returns {Promise<MockServer>}
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
async function createMockServer(options = {}) {
|
|
28
28
|
const config = {
|
|
29
29
|
port: options.port || 0,
|
|
30
30
|
healthStatus: options.healthStatus ?? 200,
|
|
@@ -202,7 +202,7 @@ export async function createMockServer(options = {}) {
|
|
|
202
202
|
* Create the portokd server for testing
|
|
203
203
|
* Sets up environment and spawns the daemon
|
|
204
204
|
*/
|
|
205
|
-
|
|
205
|
+
async function createTestDaemon(options = {}) {
|
|
206
206
|
const {
|
|
207
207
|
listenPort = 0,
|
|
208
208
|
targetPort,
|
|
@@ -226,7 +226,7 @@ export async function createTestDaemon(options = {}) {
|
|
|
226
226
|
process.env.STATE_FILE = `/tmp/portok-test-${Date.now()}.json`;
|
|
227
227
|
|
|
228
228
|
// Import the daemon (will start based on env vars)
|
|
229
|
-
const daemon =
|
|
229
|
+
const daemon = require('../../portokd.js');
|
|
230
230
|
|
|
231
231
|
// Wait for server to be ready
|
|
232
232
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -248,7 +248,7 @@ export async function createTestDaemon(options = {}) {
|
|
|
248
248
|
/**
|
|
249
249
|
* Helper to make admin requests
|
|
250
250
|
*/
|
|
251
|
-
|
|
251
|
+
async function adminRequest(baseUrl, endpoint, options = {}) {
|
|
252
252
|
const { method = 'GET', token = 'test-token-12345', body } = options;
|
|
253
253
|
|
|
254
254
|
const fetchOptions = {
|
|
@@ -276,7 +276,7 @@ export async function adminRequest(baseUrl, endpoint, options = {}) {
|
|
|
276
276
|
/**
|
|
277
277
|
* Helper to wait for a condition
|
|
278
278
|
*/
|
|
279
|
-
|
|
279
|
+
async function waitFor(condition, timeout = 5000, interval = 100) {
|
|
280
280
|
const startTime = Date.now();
|
|
281
281
|
|
|
282
282
|
while (Date.now() - startTime < timeout) {
|
|
@@ -292,7 +292,7 @@ export async function waitFor(condition, timeout = 5000, interval = 100) {
|
|
|
292
292
|
/**
|
|
293
293
|
* Helper to get a free port
|
|
294
294
|
*/
|
|
295
|
-
|
|
295
|
+
async function getFreePort() {
|
|
296
296
|
return new Promise((resolve, reject) => {
|
|
297
297
|
const server = http.createServer();
|
|
298
298
|
server.listen(0, '127.0.0.1', () => {
|
|
@@ -303,3 +303,11 @@ export async function getFreePort() {
|
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
module.exports = {
|
|
307
|
+
createMockServer,
|
|
308
|
+
createTestDaemon,
|
|
309
|
+
adminRequest,
|
|
310
|
+
waitFor,
|
|
311
|
+
getFreePort,
|
|
312
|
+
};
|
|
313
|
+
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests metrics collection: counters, RPS, inflight tracking
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Metrics', () => {
|
|
12
12
|
let mockServer;
|
|
@@ -18,7 +18,7 @@ describe('Metrics', () => {
|
|
|
18
18
|
mockServer = await createMockServer({ port: 0 });
|
|
19
19
|
proxyPort = await getFreePort();
|
|
20
20
|
|
|
21
|
-
daemonProcess = spawn('node', ['portokd.
|
|
21
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
22
22
|
env: {
|
|
23
23
|
...process.env,
|
|
24
24
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -279,7 +279,7 @@ describe('Metrics', () => {
|
|
|
279
279
|
it('should record last proxy error details', async () => {
|
|
280
280
|
// Create a daemon with invalid target port
|
|
281
281
|
const badPort = await getFreePort();
|
|
282
|
-
const badDaemon = spawn('node', ['portokd.
|
|
282
|
+
const badDaemon = spawn('node', ['portokd.js'], {
|
|
283
283
|
env: {
|
|
284
284
|
...process.env,
|
|
285
285
|
LISTEN_PORT: String(badPort),
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* Tests HTTP proxying, WebSocket upgrades, and streaming
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const { describe, it, before, after, beforeEach } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const http = require('node:http');
|
|
9
|
+
const { spawn } = require('node:child_process');
|
|
10
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
11
|
+
const WebSocket = require('ws');
|
|
12
12
|
|
|
13
13
|
describe('Proxy Core', () => {
|
|
14
14
|
let mockServer;
|
|
@@ -24,7 +24,7 @@ describe('Proxy Core', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start the daemon as a subprocess
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -36,6 +36,7 @@ describe('Proxy Core', () => {
|
|
|
36
36
|
ROLLBACK_CHECK_EVERY_MS: '500',
|
|
37
37
|
ROLLBACK_FAIL_THRESHOLD: '3',
|
|
38
38
|
ADMIN_RATE_LIMIT: '1000',
|
|
39
|
+
ENABLE_XFWD: '1',
|
|
39
40
|
},
|
|
40
41
|
stdio: 'pipe',
|
|
41
42
|
});
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests automatic rollback when health checks fail consecutively
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Auto Rollback', () => {
|
|
12
12
|
const adminToken = 'test-token-rollback';
|
|
@@ -24,7 +24,7 @@ describe('Auto Rollback', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start daemon with aggressive rollback settings for testing
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -126,7 +126,7 @@ describe('Auto Rollback', () => {
|
|
|
126
126
|
|
|
127
127
|
proxyPort = await getFreePort();
|
|
128
128
|
|
|
129
|
-
daemonProcess = spawn('node', ['portokd.
|
|
129
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
130
130
|
env: {
|
|
131
131
|
...process.env,
|
|
132
132
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -208,7 +208,7 @@ describe('Auto Rollback', () => {
|
|
|
208
208
|
|
|
209
209
|
proxyPort = await getFreePort();
|
|
210
210
|
|
|
211
|
-
daemonProcess = spawn('node', ['portokd.
|
|
211
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
212
212
|
env: {
|
|
213
213
|
...process.env,
|
|
214
214
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -280,7 +280,7 @@ describe('Auto Rollback', () => {
|
|
|
280
280
|
|
|
281
281
|
proxyPort = await getFreePort();
|
|
282
282
|
|
|
283
|
-
daemonProcess = spawn('node', ['portokd.
|
|
283
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
284
284
|
env: {
|
|
285
285
|
...process.env,
|
|
286
286
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests token validation, IP allowlist, and rate limiting
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Security', () => {
|
|
12
12
|
let mockServer;
|
|
@@ -18,7 +18,7 @@ describe('Security', () => {
|
|
|
18
18
|
mockServer = await createMockServer({ port: 0 });
|
|
19
19
|
proxyPort = await getFreePort();
|
|
20
20
|
|
|
21
|
-
daemonProcess = spawn('node', ['portokd.
|
|
21
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
22
22
|
env: {
|
|
23
23
|
...process.env,
|
|
24
24
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -187,7 +187,7 @@ describe('Security - Rate Limit Test', () => {
|
|
|
187
187
|
proxyPort = await getFreePort();
|
|
188
188
|
|
|
189
189
|
// Use rate limit of 15 - waitFor uses ~5 requests, then we make 15 more
|
|
190
|
-
daemonProcess = spawn('node', ['portokd.
|
|
190
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
191
191
|
env: {
|
|
192
192
|
...process.env,
|
|
193
193
|
LISTEN_PORT: String(proxyPort),
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Tests the /__switch endpoint with health checks
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { describe, it, before, after, beforeEach } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { spawn } = require('node:child_process');
|
|
9
|
+
const { createMockServer, getFreePort, waitFor, adminRequest } = require('./helpers/mock-server.js');
|
|
10
10
|
|
|
11
11
|
describe('Health-Gated Switching', () => {
|
|
12
12
|
let mockServer1;
|
|
@@ -24,7 +24,7 @@ describe('Health-Gated Switching', () => {
|
|
|
24
24
|
proxyPort = await getFreePort();
|
|
25
25
|
|
|
26
26
|
// Start the daemon
|
|
27
|
-
daemonProcess = spawn('node', ['portokd.
|
|
27
|
+
daemonProcess = spawn('node', ['portokd.js'], {
|
|
28
28
|
env: {
|
|
29
29
|
...process.env,
|
|
30
30
|
LISTEN_PORT: String(proxyPort),
|