pi-harness-runtime 1.1.392 → 1.1.394
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/harness/console-guard.ts +73 -0
- package/index.ts +103 -1
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/dist/client.d.ts +25 -1
- package/packages/relay/dist/client.js +71 -23
- package/packages/relay/dist/index.d.ts +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/relay/src/client.ts +87 -23
- package/packages/relay/src/index.ts +1 -1
- package/packages/relay/test/client.test.ts +134 -0
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -42,6 +42,19 @@ const CONNECT_TIMEOUT_MS = 10000;
|
|
|
42
42
|
// Client Callbacks
|
|
43
43
|
// ============================================================================
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Where the client writes its diagnostics.
|
|
47
|
+
*
|
|
48
|
+
* A TUI owns the terminal: a stray console write lands in the prompt input
|
|
49
|
+
* field. The harness therefore injects a logger that writes to the log file
|
|
50
|
+
* (the console stays the default for CLI scripts).
|
|
51
|
+
*/
|
|
52
|
+
export interface RelayLogger {
|
|
53
|
+
log?: (message: string) => void;
|
|
54
|
+
warn?: (message: string) => void;
|
|
55
|
+
error?: (message: string) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
export interface RelayClientCallbacks {
|
|
46
59
|
/** Called when connected and authenticated */
|
|
47
60
|
onConnected?: (sessionId: string) => void;
|
|
@@ -69,6 +82,7 @@ export class RelayClient {
|
|
|
69
82
|
private ws: WebSocket | null = null;
|
|
70
83
|
private config: RelayConfig;
|
|
71
84
|
private callbacks: RelayClientCallbacks;
|
|
85
|
+
private logger: RelayLogger;
|
|
72
86
|
|
|
73
87
|
// State machine
|
|
74
88
|
private state: ConnectionState = { state: 'disconnected' };
|
|
@@ -97,9 +111,57 @@ export class RelayClient {
|
|
|
97
111
|
private snapshotReceived = false;
|
|
98
112
|
private synchronizedReceived = false;
|
|
99
113
|
|
|
100
|
-
constructor(config: RelayConfig, callbacks: RelayClientCallbacks = {}) {
|
|
114
|
+
constructor(config: RelayConfig, callbacks: RelayClientCallbacks = {}, logger: RelayLogger = {}) {
|
|
101
115
|
this.config = config;
|
|
102
116
|
this.callbacks = callbacks;
|
|
117
|
+
this.logger = logger;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ========================================================================
|
|
121
|
+
// Diagnostics
|
|
122
|
+
// ========================================================================
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Write a diagnostic.
|
|
126
|
+
*
|
|
127
|
+
* A TUI owns the terminal: the harness injects a logger so that nothing
|
|
128
|
+
* reaches the console. A partial logger must not reopen that hole - a
|
|
129
|
+
* missing level falls back to a level the logger does provide.
|
|
130
|
+
*/
|
|
131
|
+
private log(message: string): void {
|
|
132
|
+
this.emit("log", message);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private logWarn(message: string): void {
|
|
136
|
+
this.emit("warn", message);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private logError(message: string): void {
|
|
140
|
+
this.emit("error", message);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private emit(level: "log" | "warn" | "error", message: string): void {
|
|
144
|
+
const logger = this.logger;
|
|
145
|
+
// A logger was injected: route the level to one the logger provides,
|
|
146
|
+
// never back to the console. The order keeps the severity as close as
|
|
147
|
+
// possible (error -> warn -> log).
|
|
148
|
+
const candidates: Array<"log" | "warn" | "error"> =
|
|
149
|
+
level === "error" ? ["error", "warn", "log"] : level === "warn" ? ["warn", "error", "log"] : ["log", "warn", "error"];
|
|
150
|
+
|
|
151
|
+
for (const candidate of candidates) {
|
|
152
|
+
const sink = candidate === "log" ? logger.log : candidate === "warn" ? logger.warn : logger.error;
|
|
153
|
+
if (!sink) continue;
|
|
154
|
+
try {
|
|
155
|
+
sink(message);
|
|
156
|
+
} catch {
|
|
157
|
+
// A broken logger must never break the relay client.
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (level === "log") console.log(message);
|
|
163
|
+
else if (level === "warn") console.warn(message);
|
|
164
|
+
else console.error(message);
|
|
103
165
|
}
|
|
104
166
|
|
|
105
167
|
// ========================================================================
|
|
@@ -167,7 +229,7 @@ export class RelayClient {
|
|
|
167
229
|
|
|
168
230
|
const url = this.config.serverUrl;
|
|
169
231
|
const timeoutMs = this.config.connectTimeout ?? CONNECT_TIMEOUT_MS;
|
|
170
|
-
|
|
232
|
+
this.log(`[RelayClient] Connecting to ${url}...`);
|
|
171
233
|
|
|
172
234
|
return new Promise<void>((resolve, reject) => {
|
|
173
235
|
const attempt = ++this.connectAttempt;
|
|
@@ -176,7 +238,7 @@ export class RelayClient {
|
|
|
176
238
|
// Deadline covers connection AND authentication (one timer for the whole handshake)
|
|
177
239
|
this.connectTimeoutTimer = setTimeout(() => {
|
|
178
240
|
if (!this.pendingConnect) return;
|
|
179
|
-
|
|
241
|
+
this.logError(`[RelayClient] Connection timeout after ${timeoutMs}ms`);
|
|
180
242
|
this.settleConnect(new Error(`Connection timeout after ${timeoutMs}ms`));
|
|
181
243
|
this.closeSocket();
|
|
182
244
|
this.setState({ state: 'disconnected' });
|
|
@@ -191,7 +253,7 @@ export class RelayClient {
|
|
|
191
253
|
// pending: settle it, disarm the deadline and retry like any other
|
|
192
254
|
// failed connect.
|
|
193
255
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
194
|
-
|
|
256
|
+
this.logError(`[RelayClient] WebSocket construction failed: ${error.message}`);
|
|
195
257
|
this.settleConnect(error);
|
|
196
258
|
this.setState({ state: 'disconnected' });
|
|
197
259
|
this.scheduleReconnectAfterFailure();
|
|
@@ -201,7 +263,7 @@ export class RelayClient {
|
|
|
201
263
|
|
|
202
264
|
ws.on("open", () => {
|
|
203
265
|
if (this.ws !== ws) return;
|
|
204
|
-
|
|
266
|
+
this.log("[RelayClient] WebSocket opened");
|
|
205
267
|
this.setState({ state: 'authenticating' });
|
|
206
268
|
|
|
207
269
|
// Send authentication
|
|
@@ -223,7 +285,7 @@ export class RelayClient {
|
|
|
223
285
|
// would otherwise corrupt the live connection (state, heartbeat) and
|
|
224
286
|
// schedule a spurious reconnect.
|
|
225
287
|
if (this.ws !== ws) return;
|
|
226
|
-
|
|
288
|
+
this.log("[RelayClient] WebSocket closed");
|
|
227
289
|
this.ws = null;
|
|
228
290
|
const wasPending = this.pendingConnect !== null;
|
|
229
291
|
this.handleDisconnect();
|
|
@@ -235,7 +297,7 @@ export class RelayClient {
|
|
|
235
297
|
|
|
236
298
|
ws.on("error", (err) => {
|
|
237
299
|
if (this.ws !== ws) return;
|
|
238
|
-
|
|
300
|
+
this.logError(`[RelayClient] WebSocket error: ${err.message}`);
|
|
239
301
|
this.callbacks.onError?.(err);
|
|
240
302
|
const wasPending = this.pendingConnect !== null;
|
|
241
303
|
this.ws = null;
|
|
@@ -275,7 +337,7 @@ export class RelayClient {
|
|
|
275
337
|
* Disconnect from relay server
|
|
276
338
|
*/
|
|
277
339
|
disconnect(): void {
|
|
278
|
-
|
|
340
|
+
this.log("[RelayClient] Disconnecting...");
|
|
279
341
|
// Deliberate stop: do not schedule a reconnect afterwards
|
|
280
342
|
this.shuttingDown = true;
|
|
281
343
|
// Never leave a caller awaiting connect() hanging
|
|
@@ -301,7 +363,7 @@ export class RelayClient {
|
|
|
301
363
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
302
364
|
this.ws.send(JSON.stringify(message));
|
|
303
365
|
} else {
|
|
304
|
-
|
|
366
|
+
this.logWarn("[RelayClient] Cannot send: WebSocket not open");
|
|
305
367
|
}
|
|
306
368
|
}
|
|
307
369
|
|
|
@@ -344,18 +406,20 @@ export class RelayClient {
|
|
|
344
406
|
|
|
345
407
|
default: {
|
|
346
408
|
const unknownMsg = msg as { type: string };
|
|
347
|
-
|
|
409
|
+
this.logWarn(`[RelayClient] Unknown message type: ${unknownMsg.type}`);
|
|
348
410
|
}
|
|
349
411
|
}
|
|
350
412
|
} catch (err) {
|
|
351
|
-
|
|
413
|
+
const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
414
|
+
// One line per log record: the stack must not break the format.
|
|
415
|
+
this.logError(`[RelayClient] Failed to parse message: ${detail.replace(/\s*\n\s*/g, " | ")}`);
|
|
352
416
|
this.callbacks.onError?.(err as Error);
|
|
353
417
|
}
|
|
354
418
|
}
|
|
355
419
|
|
|
356
420
|
private handleConnected(msg: ConnectedMessage): void {
|
|
357
421
|
this.clientId = msg.payload.clientId;
|
|
358
|
-
|
|
422
|
+
this.log(`[RelayClient] Connected, clientId: ${this.clientId}`);
|
|
359
423
|
}
|
|
360
424
|
|
|
361
425
|
private handleAuth(msg: AuthResponseMessage): void {
|
|
@@ -372,13 +436,13 @@ export class RelayClient {
|
|
|
372
436
|
|
|
373
437
|
// Notify callback
|
|
374
438
|
this.callbacks.onConnected?.(this.sessionId || '');
|
|
375
|
-
|
|
439
|
+
this.log(`[RelayClient] Authenticated, sessionId: ${this.sessionId}`);
|
|
376
440
|
|
|
377
441
|
// Readiness milestone reached - settle the connect() promise
|
|
378
442
|
this.settleConnect();
|
|
379
443
|
} else {
|
|
380
444
|
const message = msg.payload.error || 'Authentication failed';
|
|
381
|
-
|
|
445
|
+
this.logError(`[RelayClient] Auth failed: ${message}`);
|
|
382
446
|
const error = new Error(message);
|
|
383
447
|
this.callbacks.onError?.(error);
|
|
384
448
|
// Reject the caller before tearing the socket down
|
|
@@ -392,7 +456,7 @@ export class RelayClient {
|
|
|
392
456
|
}
|
|
393
457
|
|
|
394
458
|
private handleSnapshot(msg: SnapshotMessage): void {
|
|
395
|
-
|
|
459
|
+
this.log(`[RelayClient] Snapshot received: ${msg.payload.recentCallbacks.length} recent callbacks`);
|
|
396
460
|
this.snapshotReceived = true;
|
|
397
461
|
|
|
398
462
|
// Notify callback
|
|
@@ -400,7 +464,7 @@ export class RelayClient {
|
|
|
400
464
|
}
|
|
401
465
|
|
|
402
466
|
private handleSynchronized(_msg: SynchronizedMessage): void {
|
|
403
|
-
|
|
467
|
+
this.log("[RelayClient] Synchronized - live events starting");
|
|
404
468
|
this.synchronizedReceived = true;
|
|
405
469
|
|
|
406
470
|
// Notify callback
|
|
@@ -410,7 +474,7 @@ export class RelayClient {
|
|
|
410
474
|
private handleCallback(msg: CallbackMessage): void {
|
|
411
475
|
// Only process callbacks after synchronized
|
|
412
476
|
if (!this.synchronizedReceived) {
|
|
413
|
-
|
|
477
|
+
this.logWarn("[RelayClient] Ignoring callback before synchronized");
|
|
414
478
|
return;
|
|
415
479
|
}
|
|
416
480
|
|
|
@@ -418,7 +482,7 @@ export class RelayClient {
|
|
|
418
482
|
}
|
|
419
483
|
|
|
420
484
|
private handleStatus(msg: StatusMessage): void {
|
|
421
|
-
|
|
485
|
+
this.log(`[RelayClient] Status [${msg.payload.kind}]: ${msg.payload.code} - ${msg.payload.message}`);
|
|
422
486
|
this.callbacks.onStatus?.(msg.payload);
|
|
423
487
|
}
|
|
424
488
|
|
|
@@ -434,7 +498,7 @@ export class RelayClient {
|
|
|
434
498
|
|
|
435
499
|
// A deliberate stop must not trigger a reconnect
|
|
436
500
|
if (this.shuttingDown) {
|
|
437
|
-
|
|
501
|
+
this.log("[RelayClient] Disconnected (intentional stop, no reconnect)");
|
|
438
502
|
return;
|
|
439
503
|
}
|
|
440
504
|
|
|
@@ -464,12 +528,12 @@ export class RelayClient {
|
|
|
464
528
|
this.reconnectAttempt++;
|
|
465
529
|
this.setState({ state: 'reconnecting', attempt: this.reconnectAttempt });
|
|
466
530
|
|
|
467
|
-
|
|
531
|
+
this.log(`[RelayClient] Scheduling reconnect attempt ${this.reconnectAttempt} in ${this.reconnectDelay}ms`);
|
|
468
532
|
|
|
469
533
|
this.reconnectTimer = setTimeout(() => {
|
|
470
534
|
this.reconnectTimer = null;
|
|
471
535
|
this.connect().catch((err) => {
|
|
472
|
-
|
|
536
|
+
this.logError(`[RelayClient] Reconnect failed: ${err.message}`);
|
|
473
537
|
});
|
|
474
538
|
}, this.reconnectDelay);
|
|
475
539
|
|
|
@@ -545,13 +609,13 @@ export class RelayClient {
|
|
|
545
609
|
ws.removeAllListeners("error");
|
|
546
610
|
// An 'error' without a listener throws inside the ws implementation.
|
|
547
611
|
ws.on("error", (err: Error) => {
|
|
548
|
-
|
|
612
|
+
this.logWarn(`[RelayClient] Socket error while closing: ${err.message}`);
|
|
549
613
|
});
|
|
550
614
|
ws.on("close", () => {});
|
|
551
615
|
try {
|
|
552
616
|
ws.close();
|
|
553
617
|
} catch (err) {
|
|
554
|
-
|
|
618
|
+
this.logWarn(`[RelayClient] Error while closing socket: ${String(err)}`);
|
|
555
619
|
}
|
|
556
620
|
}
|
|
557
621
|
|
|
@@ -15,7 +15,7 @@ export type { RelayServerOptions } from './server.js';
|
|
|
15
15
|
|
|
16
16
|
// Client
|
|
17
17
|
export { RelayClient } from './client.js';
|
|
18
|
-
export type { RelayClientCallbacks } from './client.js';
|
|
18
|
+
export type { RelayClientCallbacks, RelayLogger } from './client.js';
|
|
19
19
|
|
|
20
20
|
// Endpoint resolution (relay URL + auth token discovery)
|
|
21
21
|
export {
|
|
@@ -135,6 +135,140 @@ describe("RelayClient.connect", () => {
|
|
|
135
135
|
await fixture.close();
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
it("writes its diagnostics to the injected logger, never to the console", async () => {
|
|
139
|
+
const fixture = await startFixture("ok");
|
|
140
|
+
const logged: string[] = [];
|
|
141
|
+
const consoleWrites: string[] = [];
|
|
142
|
+
const originalLog = console.log;
|
|
143
|
+
const originalWarn = console.warn;
|
|
144
|
+
const originalError = console.error;
|
|
145
|
+
console.log = (...args: unknown[]) => {
|
|
146
|
+
consoleWrites.push(args.map(String).join(" "));
|
|
147
|
+
};
|
|
148
|
+
console.warn = console.log;
|
|
149
|
+
console.error = console.log;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const client = new RelayClient(
|
|
153
|
+
{ serverUrl: fixture.url, token: TEST_TOKEN, connectTimeout: 2000 },
|
|
154
|
+
{},
|
|
155
|
+
{
|
|
156
|
+
log: (message) => logged.push(message),
|
|
157
|
+
warn: (message) => logged.push(message),
|
|
158
|
+
error: (message) => logged.push(message),
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await client.connect();
|
|
163
|
+
client.disconnect();
|
|
164
|
+
await fixture.close();
|
|
165
|
+
} finally {
|
|
166
|
+
console.log = originalLog;
|
|
167
|
+
console.warn = originalWarn;
|
|
168
|
+
console.error = originalError;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// The TUI owns the terminal: nothing may reach the console.
|
|
172
|
+
expect(consoleWrites.length).toBe(0);
|
|
173
|
+
expect(logged.some((message) => message.includes("[RelayClient]"))).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("routes a partial logger to the levels it has, never to the console", async () => {
|
|
177
|
+
const fixture = await startFixture("ok");
|
|
178
|
+
const logged: string[] = [];
|
|
179
|
+
const consoleWrites: string[] = [];
|
|
180
|
+
const originalLog = console.log;
|
|
181
|
+
const originalWarn = console.warn;
|
|
182
|
+
const originalError = console.error;
|
|
183
|
+
console.log = (...args: unknown[]) => {
|
|
184
|
+
consoleWrites.push(args.map(String).join(" "));
|
|
185
|
+
};
|
|
186
|
+
console.warn = console.log;
|
|
187
|
+
console.error = console.log;
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// Only "log" is provided: a warning must NOT reach the console.
|
|
191
|
+
const client = new RelayClient(
|
|
192
|
+
{ serverUrl: fixture.url, token: TEST_TOKEN, connectTimeout: 2000 },
|
|
193
|
+
{},
|
|
194
|
+
{ log: (message) => logged.push(message) },
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
await client.connect();
|
|
198
|
+
client.disconnect();
|
|
199
|
+
await fixture.close();
|
|
200
|
+
} finally {
|
|
201
|
+
console.log = originalLog;
|
|
202
|
+
console.warn = originalWarn;
|
|
203
|
+
console.error = originalError;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
expect(consoleWrites.length).toBe(0);
|
|
207
|
+
expect(logged.length).toBeGreaterThan(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("never reaches the console for any partial logger shape", async () => {
|
|
211
|
+
const shapes: Array<{ name: string; logger: { log?: (m: string) => void; warn?: (m: string) => void; error?: (m: string) => void } }> = [
|
|
212
|
+
{ name: "only log", logger: { log: () => {} } },
|
|
213
|
+
{ name: "only warn", logger: { warn: () => {} } },
|
|
214
|
+
{ name: "only error", logger: { error: () => {} } },
|
|
215
|
+
{ name: "log + error", logger: { log: () => {}, error: () => {} } },
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (const shape of shapes) {
|
|
219
|
+
const fixture = await startFixture("ok");
|
|
220
|
+
const consoleWrites: string[] = [];
|
|
221
|
+
const originalLog = console.log;
|
|
222
|
+
const originalWarn = console.warn;
|
|
223
|
+
const originalError = console.error;
|
|
224
|
+
console.log = (...args: unknown[]) => {
|
|
225
|
+
consoleWrites.push(args.map(String).join(" "));
|
|
226
|
+
};
|
|
227
|
+
console.warn = console.log;
|
|
228
|
+
console.error = console.log;
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const client = new RelayClient(
|
|
232
|
+
{ serverUrl: fixture.url, token: TEST_TOKEN, connectTimeout: 2000 },
|
|
233
|
+
{},
|
|
234
|
+
shape.logger,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
await client.connect();
|
|
238
|
+
client.disconnect();
|
|
239
|
+
await fixture.close();
|
|
240
|
+
} finally {
|
|
241
|
+
console.log = originalLog;
|
|
242
|
+
console.warn = originalWarn;
|
|
243
|
+
console.error = originalError;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
expect(consoleWrites.length).toBe(0);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("survives a logger that throws", async () => {
|
|
251
|
+
const fixture = await startFixture("ok");
|
|
252
|
+
let calls = 0;
|
|
253
|
+
const broken = (): void => {
|
|
254
|
+
calls++;
|
|
255
|
+
throw new Error("logger is broken");
|
|
256
|
+
};
|
|
257
|
+
const client = new RelayClient(
|
|
258
|
+
{ serverUrl: fixture.url, token: TEST_TOKEN, connectTimeout: 2000 },
|
|
259
|
+
{},
|
|
260
|
+
{ log: broken, warn: broken, error: broken },
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
await client.connect();
|
|
264
|
+
expect(client.getState().state).toBe("connected");
|
|
265
|
+
// The logger really ran (the test would be vacuous otherwise).
|
|
266
|
+
expect(calls).toBeGreaterThan(0);
|
|
267
|
+
|
|
268
|
+
client.disconnect();
|
|
269
|
+
await fixture.close();
|
|
270
|
+
});
|
|
271
|
+
|
|
138
272
|
it("rejects when the server never answers authentication", async () => {
|
|
139
273
|
const fixture = await startFixture("silent");
|
|
140
274
|
const client = new RelayClient({
|