@mycadet/assistant 0.12.7-dev.202610111049.d2b52a8 → 0.12.8
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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +10 -4
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +10 -4
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +10 -4
- package/openapi.yaml +6679 -658
- package/package.json +1 -1
- package/src/__tests__/config-loader-cloud-defaults.test.ts +0 -233
- package/src/__tests__/credential-health-service.test.ts +0 -14
- package/src/__tests__/credential-routes.test.ts +0 -51
- package/src/__tests__/init-feature-flag-overrides.test.ts +40 -24
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -2
- package/src/__tests__/llm-schema-fallback-profile.test.ts +9 -3
- package/src/__tests__/requests-mock.ts +231 -9
- package/src/__tests__/secret-routes-cloud-proxy.test.ts +8 -24
- package/src/__tests__/secret-routes-scrub.test.ts +2 -42
- package/src/__tests__/test-preload.ts +8 -5
- package/src/__tests__/web-search.test.ts +340 -859
- package/src/api/events/cloud-disconnected.ts +2 -2
- package/src/config/schemas/cloud.ts +2 -1
- package/src/config/schemas/host-browser.ts +1 -0
- package/src/config/schemas/ingress.ts +1 -12
- package/src/config/schemas/llm.ts +10 -1
- package/src/config/schemas/memory-lifecycle.ts +11 -1
- package/src/config/schemas/tts.ts +5 -0
- package/src/credential-execution/prompted-credential.ts +6 -18
- package/src/credential-health/credential-health-service.ts +6 -21
- package/src/daemon/credential-transcript-scrub.ts +21 -29
- package/src/ipc/gateway-client.test.ts +55 -59
- package/src/persistence/compaction-log-store-clickhouse.ts +1 -1
- package/src/persistence/llm-request-log-sink-clickhouse.ts +1 -1
- package/src/persistence/llm-request-log-source-clickhouse.ts +1 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -3
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +10 -7
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +3 -9
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +3 -3
- package/src/runtime/routes/cloud-routes.ts +14 -18
- package/src/runtime/routes/conversation-query-routes.ts +30 -230
- package/src/runtime/routes/migration-routes.ts +5 -6
- package/src/runtime/routes/secret-routes.ts +12 -31
- package/src/tools/credentials/store.ts +6 -15
- package/src/__tests__/mock-gateway-ipc.ts +0 -279
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
* - A function response receives the recorded request and returns the
|
|
24
24
|
* response dynamically.
|
|
25
25
|
* - An unmatched request throws `NoMockMatchError` listing the request and
|
|
26
|
-
* the registered handlers (CLI channel + HTTP). The gateway
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* the registered handlers (CLI channel + HTTP). The gateway transport is
|
|
27
|
+
* the exception: it answers every method — registrations are consulted
|
|
28
|
+
* first, then per-test transport defaults, then a kernel
|
|
29
|
+
* `get_feature_flags` sentinel — because incidental `get_feature_flags`
|
|
30
|
+
* calls are ubiquitous in this suite.
|
|
29
31
|
*
|
|
30
32
|
* Channels reset automatically after every test (`afterEach` registered in
|
|
31
33
|
* test-preload.ts), so registrations are per-test: anything registered at
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
* the real-socket tests rely on that.
|
|
39
41
|
*/
|
|
40
42
|
|
|
43
|
+
import { EventEmitter } from "node:events";
|
|
41
44
|
import { mock } from "bun:test";
|
|
42
45
|
|
|
43
46
|
// ---------------------------------------------------------------------------
|
|
@@ -299,16 +302,50 @@ export interface IpcMockController {
|
|
|
299
302
|
lastCall(method?: string): IpcCallRecord | undefined;
|
|
300
303
|
}
|
|
301
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Transport-level controls unique to the gateway channel. The fake answers
|
|
307
|
+
* every method (it never throws `NoMockMatchError` — incidental gateway
|
|
308
|
+
* calls are ubiquitous in the suite), so tests shape what the transport says
|
|
309
|
+
* to UN-registered requests instead.
|
|
310
|
+
*/
|
|
311
|
+
export interface GatewayTransportControls {
|
|
312
|
+
/**
|
|
313
|
+
* Results answered for methods no `.on` registration claimed. A key mapped
|
|
314
|
+
* to `undefined` suppresses even the `get_feature_flags` sentinel for that
|
|
315
|
+
* method. Replaces any previously set defaults.
|
|
316
|
+
*/
|
|
317
|
+
setDefaults(results: Record<string, unknown>): void;
|
|
318
|
+
/**
|
|
319
|
+
* Simulate a failed socket connect: the persistent client throws
|
|
320
|
+
* `IpcConnectError`, the one-shot `ipcCall` returns `undefined` (mirroring
|
|
321
|
+
* the real module).
|
|
322
|
+
*/
|
|
323
|
+
failConnect(opts?: { code?: string }): void;
|
|
324
|
+
/**
|
|
325
|
+
* Throw a transient (retryable) connect error for the first `n`
|
|
326
|
+
* persistent-client calls; with `ipcCallError`, a non-retryable
|
|
327
|
+
* `IpcCallError` instead. Consulted AFTER `.on` registrations, so a
|
|
328
|
+
* registered response is never "failed" by these knobs.
|
|
329
|
+
*/
|
|
330
|
+
failFirstN(n: number, opts?: { ipcCallError?: boolean }): void;
|
|
331
|
+
/** Persistent-client `.call` invocations for `method` since the last reset. */
|
|
332
|
+
persistentCallCount(method: string): number;
|
|
333
|
+
}
|
|
334
|
+
|
|
302
335
|
export type IpcMockChannel = "cli" | "gateway";
|
|
303
336
|
|
|
337
|
+
export async function mockIpc(
|
|
338
|
+
channel: "gateway",
|
|
339
|
+
): Promise<IpcMockController & GatewayTransportControls>;
|
|
340
|
+
export async function mockIpc(channel: "cli"): Promise<IpcMockController>;
|
|
304
341
|
export async function mockIpc(
|
|
305
342
|
channel: IpcMockChannel,
|
|
306
|
-
): Promise<IpcMockController
|
|
343
|
+
): Promise<IpcMockController & Partial<GatewayTransportControls>> {
|
|
307
344
|
if (channel === "cli") {
|
|
308
345
|
await ensureCliClientMock();
|
|
309
346
|
}
|
|
310
347
|
const target = channel === "cli" ? cliChannel : gatewayChannel;
|
|
311
|
-
|
|
348
|
+
const base: IpcMockController = {
|
|
312
349
|
on: (matcher, response) => target.on(matcher, response),
|
|
313
350
|
calls: (method) => filterIpcCalls(target, method),
|
|
314
351
|
callCount: (method) => filterIpcCalls(target, method).length,
|
|
@@ -317,6 +354,26 @@ export async function mockIpc(
|
|
|
317
354
|
return list[list.length - 1];
|
|
318
355
|
},
|
|
319
356
|
};
|
|
357
|
+
if (channel !== "gateway") {
|
|
358
|
+
return base;
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
...base,
|
|
362
|
+
setDefaults: (results) => {
|
|
363
|
+
gatewayDefaults = { ...results };
|
|
364
|
+
},
|
|
365
|
+
failConnect: (opts) => {
|
|
366
|
+
gatewayConnectError = true;
|
|
367
|
+
if (opts?.code !== undefined) {
|
|
368
|
+
gatewayConnectErrorCode = opts.code;
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
failFirstN: (n, opts) => {
|
|
372
|
+
gatewayFailFirstN = n;
|
|
373
|
+
gatewayFailFirstNIpcCallError = opts?.ipcCallError ?? false;
|
|
374
|
+
},
|
|
375
|
+
persistentCallCount: (method) => gatewayPersistentCallCounts[method] ?? 0,
|
|
376
|
+
};
|
|
320
377
|
}
|
|
321
378
|
|
|
322
379
|
/**
|
|
@@ -346,10 +403,10 @@ export function ipcError(
|
|
|
346
403
|
}
|
|
347
404
|
|
|
348
405
|
/**
|
|
349
|
-
* Hook for
|
|
350
|
-
* through to the
|
|
351
|
-
* An `Error`-instance response comes back as the resolved value so
|
|
352
|
-
* can apply the one-shot/persistent failure contract.
|
|
406
|
+
* Hook for the gateway transport fake (below): consult the gateway channel
|
|
407
|
+
* FIRST, falling through to the per-test transport defaults when nothing
|
|
408
|
+
* matches. An `Error`-instance response comes back as the resolved value so
|
|
409
|
+
* the caller can apply the one-shot/persistent failure contract.
|
|
353
410
|
*/
|
|
354
411
|
export function dispatchGatewayIpcRequest(
|
|
355
412
|
method: string,
|
|
@@ -371,6 +428,163 @@ export function dispatchGatewayIpcRequest(
|
|
|
371
428
|
}
|
|
372
429
|
}
|
|
373
430
|
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
// Gateway transport fake (installed globally once by test-preload.ts)
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Sentinel answered for `get_feature_flags` when neither a registration nor
|
|
437
|
+
* a per-test default covers it. Keeps the response non-empty so
|
|
438
|
+
* `initFeatureFlagOverrides()` does not enter its 7.75 s empty-result retry
|
|
439
|
+
* loop in tests that build the CLI program or otherwise trigger flag
|
|
440
|
+
* initialization. The key starts with a double underscore so it cannot
|
|
441
|
+
* collide with any real registry-declared flag.
|
|
442
|
+
*/
|
|
443
|
+
const GET_FEATURE_FLAGS_SENTINEL: Record<string, boolean> = {
|
|
444
|
+
__test_default__: false,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
class FakeIpcCallError extends Error {
|
|
448
|
+
readonly statusCode?: number;
|
|
449
|
+
readonly errorCode?: string;
|
|
450
|
+
readonly errorDetails?: unknown;
|
|
451
|
+
|
|
452
|
+
constructor(
|
|
453
|
+
message: string,
|
|
454
|
+
fields: {
|
|
455
|
+
statusCode?: number;
|
|
456
|
+
errorCode?: string;
|
|
457
|
+
errorDetails?: unknown;
|
|
458
|
+
} = {},
|
|
459
|
+
) {
|
|
460
|
+
super(message);
|
|
461
|
+
this.name = "IpcCallError";
|
|
462
|
+
if (fields.statusCode !== undefined) {
|
|
463
|
+
this.statusCode = fields.statusCode;
|
|
464
|
+
}
|
|
465
|
+
if (fields.errorCode !== undefined) {
|
|
466
|
+
this.errorCode = fields.errorCode;
|
|
467
|
+
}
|
|
468
|
+
if (fields.errorDetails !== undefined) {
|
|
469
|
+
this.errorDetails = fields.errorDetails;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
class FakeIpcConnectError extends Error {
|
|
475
|
+
readonly code?: string;
|
|
476
|
+
|
|
477
|
+
constructor(message: string, code?: string) {
|
|
478
|
+
super(message);
|
|
479
|
+
this.name = "IpcConnectError";
|
|
480
|
+
if (code !== undefined) {
|
|
481
|
+
this.code = code;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const RETRYABLE_CONNECT_CODES = new Set(["ENOENT", "ECONNREFUSED"]);
|
|
487
|
+
|
|
488
|
+
function isRetryableIpcConnectError(err: unknown): boolean {
|
|
489
|
+
if (typeof err === "object" && err !== null && "code" in err) {
|
|
490
|
+
const code = (err as { code?: unknown }).code;
|
|
491
|
+
return typeof code === "string" && RETRYABLE_CONNECT_CODES.has(code);
|
|
492
|
+
}
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Per-test transport state — cleared by resetRequestMockChannels.
|
|
497
|
+
let gatewayDefaults: Record<string, unknown> = {};
|
|
498
|
+
let gatewayConnectError = false;
|
|
499
|
+
let gatewayConnectErrorCode: string | undefined;
|
|
500
|
+
let gatewayFailFirstN = 0;
|
|
501
|
+
let gatewayFailFirstNIpcCallError = false;
|
|
502
|
+
const gatewayPersistentCallCounts: Record<string, number> = {};
|
|
503
|
+
|
|
504
|
+
function mockConnectError(): FakeIpcConnectError {
|
|
505
|
+
return new FakeIpcConnectError(
|
|
506
|
+
"Mock IPC socket error",
|
|
507
|
+
gatewayConnectErrorCode ?? "ENOENT",
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** The transport's answer for a method no `.on` registration claimed. */
|
|
512
|
+
function gatewayDefaultAnswer(method: string): unknown {
|
|
513
|
+
if (method in gatewayDefaults) {
|
|
514
|
+
return gatewayDefaults[method];
|
|
515
|
+
}
|
|
516
|
+
if (method === "get_feature_flags") {
|
|
517
|
+
return GET_FEATURE_FLAGS_SENTINEL;
|
|
518
|
+
}
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
class FakePersistentIpcClient extends EventEmitter {
|
|
523
|
+
async call(
|
|
524
|
+
method: string,
|
|
525
|
+
_params?: Record<string, unknown>,
|
|
526
|
+
_timeoutMs?: number,
|
|
527
|
+
): Promise<unknown> {
|
|
528
|
+
gatewayPersistentCallCounts[method] =
|
|
529
|
+
(gatewayPersistentCallCounts[method] ?? 0) + 1;
|
|
530
|
+
const dispatched = dispatchGatewayIpcRequest(method, _params);
|
|
531
|
+
if (dispatched.handled) {
|
|
532
|
+
if (dispatched.value instanceof Error) {
|
|
533
|
+
throw dispatched.value;
|
|
534
|
+
}
|
|
535
|
+
return dispatched.value;
|
|
536
|
+
}
|
|
537
|
+
if (gatewayFailFirstN > 0) {
|
|
538
|
+
gatewayFailFirstN--;
|
|
539
|
+
if (gatewayFailFirstNIpcCallError) {
|
|
540
|
+
throw new FakeIpcCallError("Mock deterministic gateway error");
|
|
541
|
+
}
|
|
542
|
+
throw mockConnectError();
|
|
543
|
+
}
|
|
544
|
+
if (gatewayConnectError) {
|
|
545
|
+
throw mockConnectError();
|
|
546
|
+
}
|
|
547
|
+
return gatewayDefaultAnswer(method);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
destroy(): void {
|
|
551
|
+
/* no-op */
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Swap `@mycadet/gateway-client/ipc-client` for the channel-backed fake.
|
|
557
|
+
* Registered once in the test preload so every test file gets a no-op IPC
|
|
558
|
+
* layer by default — no test accidentally connects to a real gateway socket.
|
|
559
|
+
*/
|
|
560
|
+
export function installGatewayIpcMock(): void {
|
|
561
|
+
// Named exports here must cover every ipc-client symbol the package barrel
|
|
562
|
+
// re-exports. Bun's mock replaces that module for relative and package
|
|
563
|
+
// imports, so a missing name fails assistant tests at load time.
|
|
564
|
+
mock.module("@mycadet/gateway-client/ipc-client", () => ({
|
|
565
|
+
ipcCall: async (
|
|
566
|
+
_socketPath: string,
|
|
567
|
+
method: string,
|
|
568
|
+
params?: Record<string, unknown>,
|
|
569
|
+
): Promise<unknown> => {
|
|
570
|
+
const dispatched = dispatchGatewayIpcRequest(method, params);
|
|
571
|
+
if (dispatched.handled) {
|
|
572
|
+
// Real ipcCall returns undefined on failure — mirror that behavior.
|
|
573
|
+
return dispatched.value instanceof Error ? undefined : dispatched.value;
|
|
574
|
+
}
|
|
575
|
+
if (gatewayConnectError) {
|
|
576
|
+
// Real ipcCall returns undefined on failure — mirror that behavior.
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
return gatewayDefaultAnswer(method);
|
|
580
|
+
},
|
|
581
|
+
IpcCallError: FakeIpcCallError,
|
|
582
|
+
IpcConnectError: FakeIpcConnectError,
|
|
583
|
+
isRetryableIpcConnectError,
|
|
584
|
+
PersistentIpcClient: FakePersistentIpcClient,
|
|
585
|
+
}));
|
|
586
|
+
}
|
|
587
|
+
|
|
374
588
|
// ---------------------------------------------------------------------------
|
|
375
589
|
// CLI client transport fake (lazy — only files that call mockIpc("cli"))
|
|
376
590
|
// ---------------------------------------------------------------------------
|
|
@@ -742,6 +956,14 @@ export function mockHttp(): HttpMockController {
|
|
|
742
956
|
export function resetRequestMockChannels(): void {
|
|
743
957
|
cliChannel.reset();
|
|
744
958
|
gatewayChannel.reset();
|
|
959
|
+
gatewayDefaults = {};
|
|
960
|
+
gatewayConnectError = false;
|
|
961
|
+
gatewayConnectErrorCode = undefined;
|
|
962
|
+
gatewayFailFirstN = 0;
|
|
963
|
+
gatewayFailFirstNIpcCallError = false;
|
|
964
|
+
for (const key of Object.keys(gatewayPersistentCallCounts)) {
|
|
965
|
+
delete gatewayPersistentCallCounts[key];
|
|
966
|
+
}
|
|
745
967
|
if (originalFetch !== undefined) {
|
|
746
968
|
globalThis.fetch = originalFetch;
|
|
747
969
|
originalFetch = undefined;
|
|
@@ -349,30 +349,14 @@ describe("secret routes cloud proxy registry sync", () => {
|
|
|
349
349
|
cloudOrganizationIdOverride = "existing-org";
|
|
350
350
|
platformUserIdOverride = "existing-user";
|
|
351
351
|
|
|
352
|
+
// Identity ids are not credentials at all — they resolve from the
|
|
353
|
+
// assistant_api_key via validate and live in the in-memory identity.
|
|
352
354
|
await expect(
|
|
353
|
-
addCredential("cadet:
|
|
354
|
-
).rejects.toThrow("Unknown cadet credential:
|
|
355
|
-
await expect(
|
|
356
|
-
addCredential("cadet:platform_organization_id", "org-1"),
|
|
357
|
-
).rejects.toThrow("Unknown cadet credential: platform_organization_id");
|
|
358
|
-
await expect(
|
|
359
|
-
addCredential("cadet:platform_user_id", "user-1"),
|
|
360
|
-
).rejects.toThrow("Unknown cadet credential: platform_user_id");
|
|
361
|
-
await expect(
|
|
362
|
-
addCredential("cadet:platform_base_url", "https://managed.example.com"),
|
|
363
|
-
).rejects.toThrow("Unknown cadet credential: platform_base_url");
|
|
355
|
+
addCredential("cadet:cloud_assistant_id", "asst-1"),
|
|
356
|
+
).rejects.toThrow("Unknown cadet credential: cloud_assistant_id");
|
|
364
357
|
|
|
365
358
|
expect(
|
|
366
|
-
secureKeyStore.get(credentialKey("cadet", "
|
|
367
|
-
).toBeUndefined();
|
|
368
|
-
expect(
|
|
369
|
-
secureKeyStore.get(credentialKey("cadet", "platform_organization_id")),
|
|
370
|
-
).toBeUndefined();
|
|
371
|
-
expect(
|
|
372
|
-
secureKeyStore.get(credentialKey("cadet", "platform_user_id")),
|
|
373
|
-
).toBeUndefined();
|
|
374
|
-
expect(
|
|
375
|
-
secureKeyStore.get(credentialKey("cadet", "platform_base_url")),
|
|
359
|
+
secureKeyStore.get(credentialKey("cadet", "cloud_assistant_id")),
|
|
376
360
|
).toBeUndefined();
|
|
377
361
|
expect(metadataUpserts).toEqual([]);
|
|
378
362
|
expect(cloudAssistantIdOverride).toBe("existing-asst");
|
|
@@ -383,13 +367,13 @@ describe("secret routes cloud proxy registry sync", () => {
|
|
|
383
367
|
});
|
|
384
368
|
|
|
385
369
|
test("an unknown cadet credential does not delete leftover vault copies", async () => {
|
|
386
|
-
const key = credentialKey("cadet", "
|
|
370
|
+
const key = credentialKey("cadet", "cloud_assistant_id");
|
|
387
371
|
secureKeyStore.set(key, "leftover-asst");
|
|
388
372
|
cloudAssistantIdOverride = "asst-1";
|
|
389
373
|
|
|
390
374
|
await expect(
|
|
391
|
-
addCredential("cadet:
|
|
392
|
-
).rejects.toThrow("Unknown cadet credential:
|
|
375
|
+
addCredential("cadet:cloud_assistant_id", " "),
|
|
376
|
+
).rejects.toThrow("Unknown cadet credential: cloud_assistant_id");
|
|
393
377
|
|
|
394
378
|
expect(secureKeyStore.get(key)).toBe("leftover-asst");
|
|
395
379
|
expect(cloudAssistantIdOverride).toBe("asst-1");
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* - scrubs the stored value exactly once, immediately after the
|
|
7
7
|
* secure-store write
|
|
8
8
|
* - never scrubs when validation rejects the request (nothing was stored)
|
|
9
|
-
* - still succeeds when the scrub itself rejects (best-effort hygiene)
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -21,7 +20,6 @@ import { useMemorySecureKeyMap } from "./helpers/secure-keys.js";
|
|
|
21
20
|
const secureStore = useMemorySecureKeyMap();
|
|
22
21
|
|
|
23
22
|
let scrubbedValues: string[];
|
|
24
|
-
let scrubRejects: boolean;
|
|
25
23
|
let anthropicKeyValid: boolean;
|
|
26
24
|
let providersRefreshed: number;
|
|
27
25
|
|
|
@@ -32,9 +30,6 @@ let providersRefreshed: number;
|
|
|
32
30
|
mock.module("../daemon/credential-transcript-scrub.js", () => ({
|
|
33
31
|
scrubStoredCredentialFromTranscripts: mock(async (value: string) => {
|
|
34
32
|
scrubbedValues.push(value);
|
|
35
|
-
if (scrubRejects) {
|
|
36
|
-
throw new Error("transcript sweep failed");
|
|
37
|
-
}
|
|
38
33
|
return { dbMessagesScrubbed: 0, residentMessagesScrubbed: 0 };
|
|
39
34
|
}),
|
|
40
35
|
}));
|
|
@@ -87,7 +82,6 @@ describe("secrets_add api_key transcript scrub", () => {
|
|
|
87
82
|
beforeEach(() => {
|
|
88
83
|
secureStore.clear();
|
|
89
84
|
scrubbedValues = [];
|
|
90
|
-
scrubRejects = false;
|
|
91
85
|
anthropicKeyValid = true;
|
|
92
86
|
providersRefreshed = 0;
|
|
93
87
|
});
|
|
@@ -104,6 +98,8 @@ describe("secrets_add api_key transcript scrub", () => {
|
|
|
104
98
|
API_KEY_VALUE,
|
|
105
99
|
);
|
|
106
100
|
expect(scrubbedValues).toEqual([API_KEY_VALUE]);
|
|
101
|
+
// The provider refresh still happens after the scrub.
|
|
102
|
+
expect(providersRefreshed).toBe(1);
|
|
107
103
|
});
|
|
108
104
|
|
|
109
105
|
test("an unknown provider is rejected and never triggers a scrub", async () => {
|
|
@@ -134,23 +130,6 @@ describe("secrets_add api_key transcript scrub", () => {
|
|
|
134
130
|
expect(secureStore.size).toBe(0);
|
|
135
131
|
expect(scrubbedValues).toEqual([]);
|
|
136
132
|
});
|
|
137
|
-
|
|
138
|
-
test("the route still succeeds (and refreshes providers) when the scrub rejects", async () => {
|
|
139
|
-
scrubRejects = true;
|
|
140
|
-
|
|
141
|
-
const result = await addRoute.handler({
|
|
142
|
-
body: { type: "api_key", name: "fireworks", value: API_KEY_VALUE },
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
expect(result).toEqual(expect.objectContaining({ success: true }));
|
|
146
|
-
expect(secureStore.get(credentialKey("fireworks", "api_key"))).toBe(
|
|
147
|
-
API_KEY_VALUE,
|
|
148
|
-
);
|
|
149
|
-
// The scrub ran (once) even though it failed, and the failure stayed
|
|
150
|
-
// invisible to the caller — the provider refresh still happened.
|
|
151
|
-
expect(scrubbedValues).toEqual([API_KEY_VALUE]);
|
|
152
|
-
expect(providersRefreshed).toBe(1);
|
|
153
|
-
});
|
|
154
133
|
});
|
|
155
134
|
|
|
156
135
|
const CREDENTIAL_VALUE = "synthetic-credential-value-0987654321";
|
|
@@ -159,7 +138,6 @@ describe("secrets_add credential transcript scrub", () => {
|
|
|
159
138
|
beforeEach(() => {
|
|
160
139
|
secureStore.clear();
|
|
161
140
|
scrubbedValues = [];
|
|
162
|
-
scrubRejects = false;
|
|
163
141
|
anthropicKeyValid = true;
|
|
164
142
|
providersRefreshed = 0;
|
|
165
143
|
});
|
|
@@ -180,24 +158,6 @@ describe("secrets_add credential transcript scrub", () => {
|
|
|
180
158
|
expect(scrubbedValues).toEqual([CREDENTIAL_VALUE]);
|
|
181
159
|
});
|
|
182
160
|
|
|
183
|
-
test("the route still succeeds when the credential scrub rejects", async () => {
|
|
184
|
-
scrubRejects = true;
|
|
185
|
-
|
|
186
|
-
const result = await addRoute.handler({
|
|
187
|
-
body: {
|
|
188
|
-
type: "credential",
|
|
189
|
-
name: "github:api_token",
|
|
190
|
-
value: CREDENTIAL_VALUE,
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
expect(result).toEqual(expect.objectContaining({ success: true }));
|
|
195
|
-
expect(secureStore.get(credentialKey("github", "api_token"))).toBe(
|
|
196
|
-
CREDENTIAL_VALUE,
|
|
197
|
-
);
|
|
198
|
-
expect(scrubbedValues).toEqual([CREDENTIAL_VALUE]);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
161
|
test("an unknown cadet credential is rejected and never triggers a scrub", async () => {
|
|
202
162
|
await expect(
|
|
203
163
|
addRoute.handler({
|
|
@@ -22,8 +22,10 @@ import { tmpdir } from "node:os";
|
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import { afterAll, afterEach } from "bun:test";
|
|
24
24
|
|
|
25
|
-
import {
|
|
26
|
-
|
|
25
|
+
import {
|
|
26
|
+
installGatewayIpcMock,
|
|
27
|
+
resetRequestMockChannels,
|
|
28
|
+
} from "./requests-mock.js";
|
|
27
29
|
|
|
28
30
|
// --- Phase 1: env override (zero source-module imports above this point) ---
|
|
29
31
|
|
|
@@ -67,9 +69,10 @@ delete process.env.CES_CREDENTIAL_URL;
|
|
|
67
69
|
// --- Phase 2: install the IPC mock (no source-module imports) ---
|
|
68
70
|
|
|
69
71
|
// Mock gateway IPC so no test accidentally connects to a real gateway socket.
|
|
70
|
-
// The
|
|
71
|
-
// retry loop in `initFeatureFlagOverrides()`. Tests that need specific
|
|
72
|
-
//
|
|
72
|
+
// The transport answers a sentinel for `get_feature_flags` to short-circuit
|
|
73
|
+
// the retry loop in `initFeatureFlagOverrides()`. Tests that need specific
|
|
74
|
+
// IPC behavior use `mockIpc("gateway")` (`.on`, `.setDefaults`,
|
|
75
|
+
// `.failConnect`, `.failFirstN`).
|
|
73
76
|
installGatewayIpcMock();
|
|
74
77
|
|
|
75
78
|
// requests-mock channels (mockIpc/mockHttp) are per-test by construction:
|