@vellumai/assistant 0.4.50 → 0.4.51
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/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +19 -18
- package/src/__tests__/credential-vault.test.ts +17 -17
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +121 -32
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/oauth-cli.test.ts +286 -60
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +243 -11
- package/src/__tests__/relay-server.test.ts +9 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +71 -16
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +58 -9
- package/src/calls/call-domain.ts +3 -4
- package/src/calls/relay-server.ts +1 -1
- package/src/calls/twilio-config.ts +4 -3
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +7 -5
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/oauth/apps.ts +40 -11
- package/src/cli/commands/oauth/connections.ts +66 -30
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +3 -3
- package/src/cli.ts +16 -12
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/loader.ts +6 -42
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/daemon/config-watcher.ts +8 -13
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/lifecycle.ts +8 -1
- package/src/daemon/message-types/sessions.ts +0 -42
- package/src/daemon/server.ts +0 -6
- package/src/daemon/session-slash.ts +3 -5
- package/src/email/providers/index.ts +2 -2
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/conversation-queries.ts +3 -80
- package/src/memory/db-init.ts +4 -0
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/migrations/149-oauth-tables.ts +1 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +1 -1
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +32 -24
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +83 -17
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +208 -8
- package/src/oauth/token-persistence.ts +20 -13
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +71 -29
- package/src/runtime/invite-service.ts +40 -22
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +47 -10
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/workspace-routes.ts +7 -3
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +19 -16
- package/src/security/token-manager.ts +13 -6
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/tools/credentials/vault.ts +22 -5
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +2 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
|
@@ -46,6 +46,16 @@ let mockUpsertAppResult: Record<string, unknown> = {
|
|
|
46
46
|
createdAt: 1700000000000,
|
|
47
47
|
updatedAt: 1700000000000,
|
|
48
48
|
};
|
|
49
|
+
let mockUpsertAppImpl:
|
|
50
|
+
| ((
|
|
51
|
+
provider: string,
|
|
52
|
+
clientId: string,
|
|
53
|
+
clientSecretOpts?: {
|
|
54
|
+
clientSecretValue?: string;
|
|
55
|
+
clientSecretCredentialPath?: string;
|
|
56
|
+
},
|
|
57
|
+
) => Promise<Record<string, unknown>>)
|
|
58
|
+
| undefined;
|
|
49
59
|
|
|
50
60
|
// Connect mock state
|
|
51
61
|
let mockOrchestrateOAuthConnect: (
|
|
@@ -65,6 +75,10 @@ let mockGetProviderBehavior: (
|
|
|
65
75
|
providerKey: string,
|
|
66
76
|
) => Record<string, unknown> | undefined = () => undefined;
|
|
67
77
|
let mockGetSecureKey: (account: string) => string | undefined = () => undefined;
|
|
78
|
+
let mockGetCredentialMetadata: (
|
|
79
|
+
service: string,
|
|
80
|
+
field: string,
|
|
81
|
+
) => Record<string, unknown> | undefined = () => undefined;
|
|
68
82
|
|
|
69
83
|
function nextUUID(): string {
|
|
70
84
|
idCounter += 1;
|
|
@@ -116,6 +130,9 @@ mock.module("../oauth/oauth-store.js", () => ({
|
|
|
116
130
|
clientSecretCredentialPath?: string;
|
|
117
131
|
},
|
|
118
132
|
) => {
|
|
133
|
+
if (mockUpsertAppImpl) {
|
|
134
|
+
return mockUpsertAppImpl(provider, clientId, clientSecretOpts);
|
|
135
|
+
}
|
|
119
136
|
mockUpsertAppCalls.push({ provider, clientId, clientSecretOpts });
|
|
120
137
|
return mockUpsertAppResult;
|
|
121
138
|
},
|
|
@@ -164,7 +181,8 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
164
181
|
|
|
165
182
|
mock.module("../tools/credentials/metadata-store.js", () => ({
|
|
166
183
|
assertMetadataWritable: () => {},
|
|
167
|
-
getCredentialMetadata: () =>
|
|
184
|
+
getCredentialMetadata: (service: string, field: string) =>
|
|
185
|
+
mockGetCredentialMetadata(service, field),
|
|
168
186
|
upsertCredentialMetadata: () => ({}),
|
|
169
187
|
listCredentialMetadata: () => [],
|
|
170
188
|
deleteCredentialMetadata: (service: string, field: string): boolean => {
|
|
@@ -319,11 +337,11 @@ describe("assistant oauth connections token <provider-key>", () => {
|
|
|
319
337
|
const { exitCode, stdout } = await runCli([
|
|
320
338
|
"connections",
|
|
321
339
|
"token",
|
|
322
|
-
"integration:
|
|
340
|
+
"integration:google",
|
|
323
341
|
]);
|
|
324
342
|
expect(exitCode).toBe(0);
|
|
325
343
|
expect(stdout).toBe("gmail-token\n");
|
|
326
|
-
expect(capturedService).toBe("integration:
|
|
344
|
+
expect(capturedService).toBe("integration:google");
|
|
327
345
|
});
|
|
328
346
|
|
|
329
347
|
test("exits 1 when no token exists", async () => {
|
|
@@ -403,30 +421,30 @@ describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
|
403
421
|
const result = await runCli([
|
|
404
422
|
"connections",
|
|
405
423
|
"disconnect",
|
|
406
|
-
"integration:
|
|
424
|
+
"integration:google",
|
|
407
425
|
"--json",
|
|
408
426
|
]);
|
|
409
427
|
expect(result.exitCode).toBe(0);
|
|
410
428
|
const parsed = JSON.parse(result.stdout);
|
|
411
429
|
expect(parsed.ok).toBe(true);
|
|
412
|
-
expect(parsed.service).toBe("integration:
|
|
430
|
+
expect(parsed.service).toBe("integration:google");
|
|
413
431
|
|
|
414
432
|
// disconnectOAuthProvider should have been called with the full provider key
|
|
415
|
-
expect(disconnectOAuthProviderCalls).toEqual(["integration:
|
|
433
|
+
expect(disconnectOAuthProviderCalls).toEqual(["integration:google"]);
|
|
416
434
|
});
|
|
417
435
|
|
|
418
436
|
test("reports not-found when nothing exists", async () => {
|
|
419
437
|
const result = await runCli([
|
|
420
438
|
"connections",
|
|
421
439
|
"disconnect",
|
|
422
|
-
"integration:
|
|
440
|
+
"integration:google",
|
|
423
441
|
"--json",
|
|
424
442
|
]);
|
|
425
443
|
expect(result.exitCode).toBe(1);
|
|
426
444
|
const parsed = JSON.parse(result.stdout);
|
|
427
445
|
expect(parsed.ok).toBe(false);
|
|
428
446
|
expect(parsed.error).toContain("No OAuth connection or credentials");
|
|
429
|
-
expect(parsed.error).toContain("integration:
|
|
447
|
+
expect(parsed.error).toContain("integration:google");
|
|
430
448
|
});
|
|
431
449
|
|
|
432
450
|
test("cleans up legacy credential keys if present", async () => {
|
|
@@ -439,12 +457,12 @@ describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
|
439
457
|
];
|
|
440
458
|
for (const field of legacyFields) {
|
|
441
459
|
secureKeyStore.set(
|
|
442
|
-
credentialKey("integration:
|
|
460
|
+
credentialKey("integration:google", field),
|
|
443
461
|
`legacy_${field}_value`,
|
|
444
462
|
);
|
|
445
463
|
metadataStore.push({
|
|
446
464
|
credentialId: nextUUID(),
|
|
447
|
-
service: "integration:
|
|
465
|
+
service: "integration:google",
|
|
448
466
|
field,
|
|
449
467
|
allowedTools: [],
|
|
450
468
|
allowedDomains: [],
|
|
@@ -456,22 +474,22 @@ describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
|
456
474
|
const result = await runCli([
|
|
457
475
|
"connections",
|
|
458
476
|
"disconnect",
|
|
459
|
-
"integration:
|
|
477
|
+
"integration:google",
|
|
460
478
|
"--json",
|
|
461
479
|
]);
|
|
462
480
|
expect(result.exitCode).toBe(0);
|
|
463
481
|
const parsed = JSON.parse(result.stdout);
|
|
464
482
|
expect(parsed.ok).toBe(true);
|
|
465
|
-
expect(parsed.service).toBe("integration:
|
|
483
|
+
expect(parsed.service).toBe("integration:google");
|
|
466
484
|
|
|
467
485
|
// All legacy keys should be removed
|
|
468
486
|
for (const field of legacyFields) {
|
|
469
487
|
expect(
|
|
470
|
-
secureKeyStore.has(credentialKey("integration:
|
|
488
|
+
secureKeyStore.has(credentialKey("integration:google", field)),
|
|
471
489
|
).toBe(false);
|
|
472
490
|
expect(
|
|
473
491
|
metadataStore.find(
|
|
474
|
-
(m) => m.service === "integration:
|
|
492
|
+
(m) => m.service === "integration:google" && m.field === field,
|
|
475
493
|
),
|
|
476
494
|
).toBeUndefined();
|
|
477
495
|
}
|
|
@@ -483,12 +501,12 @@ describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
|
483
501
|
|
|
484
502
|
// Seed a legacy credential key
|
|
485
503
|
secureKeyStore.set(
|
|
486
|
-
credentialKey("integration:
|
|
504
|
+
credentialKey("integration:google", "access_token"),
|
|
487
505
|
"legacy_token",
|
|
488
506
|
);
|
|
489
507
|
metadataStore.push({
|
|
490
508
|
credentialId: nextUUID(),
|
|
491
|
-
service: "integration:
|
|
509
|
+
service: "integration:google",
|
|
492
510
|
field: "access_token",
|
|
493
511
|
allowedTools: [],
|
|
494
512
|
allowedDomains: [],
|
|
@@ -499,7 +517,7 @@ describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
|
499
517
|
const result = await runCli([
|
|
500
518
|
"connections",
|
|
501
519
|
"disconnect",
|
|
502
|
-
"integration:
|
|
520
|
+
"integration:google",
|
|
503
521
|
"--json",
|
|
504
522
|
]);
|
|
505
523
|
expect(result.exitCode).toBe(0);
|
|
@@ -507,9 +525,9 @@ describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
|
507
525
|
expect(parsed.ok).toBe(true);
|
|
508
526
|
|
|
509
527
|
// Both should be cleaned up
|
|
510
|
-
expect(disconnectOAuthProviderCalls).toEqual(["integration:
|
|
528
|
+
expect(disconnectOAuthProviderCalls).toEqual(["integration:google"]);
|
|
511
529
|
expect(
|
|
512
|
-
secureKeyStore.has(credentialKey("integration:
|
|
530
|
+
secureKeyStore.has(credentialKey("integration:google", "access_token")),
|
|
513
531
|
).toBe(false);
|
|
514
532
|
});
|
|
515
533
|
});
|
|
@@ -521,7 +539,7 @@ describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
|
521
539
|
describe("assistant oauth providers list", () => {
|
|
522
540
|
const fakeProviders = [
|
|
523
541
|
{
|
|
524
|
-
providerKey: "integration:
|
|
542
|
+
providerKey: "integration:google",
|
|
525
543
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
526
544
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
527
545
|
defaultScopes: "[]",
|
|
@@ -578,7 +596,7 @@ describe("assistant oauth providers list", () => {
|
|
|
578
596
|
const parsed = JSON.parse(stdout);
|
|
579
597
|
expect(parsed).toHaveLength(4);
|
|
580
598
|
const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
|
|
581
|
-
expect(keys).toContain("integration:
|
|
599
|
+
expect(keys).toContain("integration:google");
|
|
582
600
|
expect(keys).toContain("integration:google-calendar");
|
|
583
601
|
expect(keys).toContain("integration:slack");
|
|
584
602
|
expect(keys).toContain("integration:twitter");
|
|
@@ -589,13 +607,13 @@ describe("assistant oauth providers list", () => {
|
|
|
589
607
|
"providers",
|
|
590
608
|
"list",
|
|
591
609
|
"--provider-key",
|
|
592
|
-
"
|
|
610
|
+
"slack",
|
|
593
611
|
"--json",
|
|
594
612
|
]);
|
|
595
613
|
expect(exitCode).toBe(0);
|
|
596
614
|
const parsed = JSON.parse(stdout);
|
|
597
615
|
expect(parsed).toHaveLength(1);
|
|
598
|
-
expect(parsed[0].providerKey).toBe("integration:
|
|
616
|
+
expect(parsed[0].providerKey).toBe("integration:slack");
|
|
599
617
|
});
|
|
600
618
|
|
|
601
619
|
test("filters by comma-separated OR values", async () => {
|
|
@@ -603,15 +621,16 @@ describe("assistant oauth providers list", () => {
|
|
|
603
621
|
"providers",
|
|
604
622
|
"list",
|
|
605
623
|
"--provider-key",
|
|
606
|
-
"
|
|
624
|
+
"slack,google",
|
|
607
625
|
"--json",
|
|
608
626
|
]);
|
|
609
627
|
expect(exitCode).toBe(0);
|
|
610
628
|
const parsed = JSON.parse(stdout);
|
|
611
|
-
expect(parsed).toHaveLength(
|
|
629
|
+
expect(parsed).toHaveLength(3);
|
|
612
630
|
const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
|
|
613
|
-
expect(keys).toContain("integration:
|
|
631
|
+
expect(keys).toContain("integration:google");
|
|
614
632
|
expect(keys).toContain("integration:google-calendar");
|
|
633
|
+
expect(keys).toContain("integration:slack");
|
|
615
634
|
});
|
|
616
635
|
|
|
617
636
|
test("returns empty array when comma-separated filter has no matches", async () => {
|
|
@@ -632,15 +651,16 @@ describe("assistant oauth providers list", () => {
|
|
|
632
651
|
"providers",
|
|
633
652
|
"list",
|
|
634
653
|
"--provider-key",
|
|
635
|
-
"
|
|
654
|
+
"slack, google",
|
|
636
655
|
"--json",
|
|
637
656
|
]);
|
|
638
657
|
expect(exitCode).toBe(0);
|
|
639
658
|
const parsed = JSON.parse(stdout);
|
|
640
|
-
expect(parsed).toHaveLength(
|
|
659
|
+
expect(parsed).toHaveLength(3);
|
|
641
660
|
const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
|
|
642
|
-
expect(keys).toContain("integration:
|
|
661
|
+
expect(keys).toContain("integration:google");
|
|
643
662
|
expect(keys).toContain("integration:google-calendar");
|
|
663
|
+
expect(keys).toContain("integration:slack");
|
|
644
664
|
});
|
|
645
665
|
|
|
646
666
|
test("ignores empty segments from extra commas in --provider-key", async () => {
|
|
@@ -648,15 +668,16 @@ describe("assistant oauth providers list", () => {
|
|
|
648
668
|
"providers",
|
|
649
669
|
"list",
|
|
650
670
|
"--provider-key",
|
|
651
|
-
"
|
|
671
|
+
"slack,,google",
|
|
652
672
|
"--json",
|
|
653
673
|
]);
|
|
654
674
|
expect(exitCode).toBe(0);
|
|
655
675
|
const parsed = JSON.parse(stdout);
|
|
656
|
-
expect(parsed).toHaveLength(
|
|
676
|
+
expect(parsed).toHaveLength(3);
|
|
657
677
|
const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
|
|
658
|
-
expect(keys).toContain("integration:
|
|
678
|
+
expect(keys).toContain("integration:google");
|
|
659
679
|
expect(keys).toContain("integration:google-calendar");
|
|
680
|
+
expect(keys).toContain("integration:slack");
|
|
660
681
|
});
|
|
661
682
|
});
|
|
662
683
|
|
|
@@ -685,6 +706,14 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
685
706
|
});
|
|
686
707
|
|
|
687
708
|
test("completes interactive flow and prints success (human mode)", async () => {
|
|
709
|
+
mockGetAppByProviderAndClientId = () => ({
|
|
710
|
+
id: "app-1",
|
|
711
|
+
clientId: "test-id",
|
|
712
|
+
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
713
|
+
providerKey: "integration:google",
|
|
714
|
+
createdAt: 0,
|
|
715
|
+
updatedAt: 0,
|
|
716
|
+
});
|
|
688
717
|
mockOrchestrateOAuthConnect = async () => ({
|
|
689
718
|
success: true,
|
|
690
719
|
deferred: false,
|
|
@@ -695,7 +724,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
695
724
|
const { exitCode, stdout } = await runCli([
|
|
696
725
|
"connections",
|
|
697
726
|
"connect",
|
|
698
|
-
"integration:
|
|
727
|
+
"integration:google",
|
|
699
728
|
"--client-id",
|
|
700
729
|
"test-id",
|
|
701
730
|
]);
|
|
@@ -704,6 +733,14 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
704
733
|
});
|
|
705
734
|
|
|
706
735
|
test("completes interactive flow and returns JSON with --json flag", async () => {
|
|
736
|
+
mockGetAppByProviderAndClientId = () => ({
|
|
737
|
+
id: "app-1",
|
|
738
|
+
clientId: "test-id",
|
|
739
|
+
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
740
|
+
providerKey: "integration:google",
|
|
741
|
+
createdAt: 0,
|
|
742
|
+
updatedAt: 0,
|
|
743
|
+
});
|
|
707
744
|
mockOrchestrateOAuthConnect = async () => ({
|
|
708
745
|
success: true,
|
|
709
746
|
deferred: false,
|
|
@@ -714,7 +751,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
714
751
|
const { exitCode, stdout } = await runCli([
|
|
715
752
|
"connections",
|
|
716
753
|
"connect",
|
|
717
|
-
"integration:
|
|
754
|
+
"integration:google",
|
|
718
755
|
"--client-id",
|
|
719
756
|
"test-id",
|
|
720
757
|
"--json",
|
|
@@ -729,18 +766,26 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
729
766
|
});
|
|
730
767
|
|
|
731
768
|
test("returns auth URL in default (non-interactive) mode (JSON)", async () => {
|
|
769
|
+
mockGetAppByProviderAndClientId = () => ({
|
|
770
|
+
id: "app-1",
|
|
771
|
+
clientId: "test-id",
|
|
772
|
+
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
773
|
+
providerKey: "integration:google",
|
|
774
|
+
createdAt: 0,
|
|
775
|
+
updatedAt: 0,
|
|
776
|
+
});
|
|
732
777
|
mockOrchestrateOAuthConnect = async () => ({
|
|
733
778
|
success: true,
|
|
734
779
|
deferred: true,
|
|
735
780
|
authUrl: "https://example.com/auth",
|
|
736
781
|
state: "abc",
|
|
737
|
-
service: "integration:
|
|
782
|
+
service: "integration:google",
|
|
738
783
|
});
|
|
739
784
|
|
|
740
785
|
const { exitCode, stdout } = await runCli([
|
|
741
786
|
"connections",
|
|
742
787
|
"connect",
|
|
743
|
-
"integration:
|
|
788
|
+
"integration:google",
|
|
744
789
|
"--client-id",
|
|
745
790
|
"test-id",
|
|
746
791
|
"--json",
|
|
@@ -758,7 +803,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
758
803
|
const { exitCode, stdout } = await runCli([
|
|
759
804
|
"connections",
|
|
760
805
|
"connect",
|
|
761
|
-
"integration:
|
|
806
|
+
"integration:google",
|
|
762
807
|
"--json",
|
|
763
808
|
]);
|
|
764
809
|
expect(exitCode).toBe(1);
|
|
@@ -772,7 +817,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
772
817
|
id: "app-1",
|
|
773
818
|
clientId: "db-client-id",
|
|
774
819
|
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
775
|
-
providerKey: "integration:
|
|
820
|
+
providerKey: "integration:google",
|
|
776
821
|
createdAt: 0,
|
|
777
822
|
updatedAt: 0,
|
|
778
823
|
});
|
|
@@ -787,7 +832,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
787
832
|
};
|
|
788
833
|
};
|
|
789
834
|
|
|
790
|
-
await runCli(["connections", "connect", "integration:
|
|
835
|
+
await runCli(["connections", "connect", "integration:google"]);
|
|
791
836
|
expect(capturedClientId).toBe("db-client-id");
|
|
792
837
|
});
|
|
793
838
|
|
|
@@ -796,7 +841,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
796
841
|
id: "app-1",
|
|
797
842
|
clientId: "db-client-id",
|
|
798
843
|
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
799
|
-
providerKey: "integration:
|
|
844
|
+
providerKey: "integration:google",
|
|
800
845
|
createdAt: 0,
|
|
801
846
|
updatedAt: 0,
|
|
802
847
|
});
|
|
@@ -814,13 +859,21 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
814
859
|
};
|
|
815
860
|
};
|
|
816
861
|
|
|
817
|
-
await runCli(["connections", "connect", "integration:
|
|
862
|
+
await runCli(["connections", "connect", "integration:google"]);
|
|
818
863
|
expect(capturedOpts).toBeDefined();
|
|
819
864
|
expect(capturedOpts!.clientId).toBe("db-client-id");
|
|
820
865
|
expect(capturedOpts!.clientSecret).toBe("db-secret");
|
|
821
866
|
});
|
|
822
867
|
|
|
823
868
|
test("outputs error from orchestrator", async () => {
|
|
869
|
+
mockGetAppByProviderAndClientId = () => ({
|
|
870
|
+
id: "app-1",
|
|
871
|
+
clientId: "x",
|
|
872
|
+
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
873
|
+
providerKey: "integration:google",
|
|
874
|
+
createdAt: 0,
|
|
875
|
+
updatedAt: 0,
|
|
876
|
+
});
|
|
824
877
|
mockOrchestrateOAuthConnect = async () => ({
|
|
825
878
|
success: false,
|
|
826
879
|
error: "Something went wrong",
|
|
@@ -829,7 +882,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
829
882
|
const { exitCode, stdout } = await runCli([
|
|
830
883
|
"connections",
|
|
831
884
|
"connect",
|
|
832
|
-
"integration:
|
|
885
|
+
"integration:google",
|
|
833
886
|
"--client-id",
|
|
834
887
|
"x",
|
|
835
888
|
"--json",
|
|
@@ -840,7 +893,83 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
840
893
|
expect(parsed.error).toBe("Something went wrong");
|
|
841
894
|
});
|
|
842
895
|
|
|
896
|
+
test("succeeds when callbackTransport is null (loopback default)", async () => {
|
|
897
|
+
// Provider row has callbackTransport: null — orchestrator should default
|
|
898
|
+
// to loopback and not require a public ingress URL.
|
|
899
|
+
mockGetMostRecentAppByProvider = () => ({
|
|
900
|
+
id: "app-loopback",
|
|
901
|
+
clientId: "loopback-client",
|
|
902
|
+
clientSecretCredentialPath: "oauth_app/app-loopback/client_secret",
|
|
903
|
+
providerKey: "integration:test-loopback",
|
|
904
|
+
createdAt: 0,
|
|
905
|
+
updatedAt: 0,
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
let capturedOpts: Record<string, unknown> | undefined;
|
|
909
|
+
mockOrchestrateOAuthConnect = async (opts) => {
|
|
910
|
+
capturedOpts = opts;
|
|
911
|
+
return {
|
|
912
|
+
success: true,
|
|
913
|
+
deferred: true,
|
|
914
|
+
authUrl: "https://example.com/auth?loopback",
|
|
915
|
+
state: "state-loopback",
|
|
916
|
+
service: "integration:test-loopback",
|
|
917
|
+
};
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
const { exitCode, stdout } = await runCli([
|
|
921
|
+
"connections",
|
|
922
|
+
"connect",
|
|
923
|
+
"integration:test-loopback",
|
|
924
|
+
"--json",
|
|
925
|
+
]);
|
|
926
|
+
expect(exitCode).toBe(0);
|
|
927
|
+
const parsed = JSON.parse(stdout);
|
|
928
|
+
expect(parsed.ok).toBe(true);
|
|
929
|
+
expect(parsed.deferred).toBe(true);
|
|
930
|
+
expect(capturedOpts).toBeDefined();
|
|
931
|
+
expect(capturedOpts!.clientId).toBe("loopback-client");
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
test("returns ingress URL error when callbackTransport is explicitly gateway", async () => {
|
|
935
|
+
// Provider row has callbackTransport: "gateway" — orchestrator should
|
|
936
|
+
// require a public ingress URL, which is not configured in the test env.
|
|
937
|
+
mockGetMostRecentAppByProvider = () => ({
|
|
938
|
+
id: "app-gateway",
|
|
939
|
+
clientId: "gateway-client",
|
|
940
|
+
clientSecretCredentialPath: "oauth_app/app-gateway/client_secret",
|
|
941
|
+
providerKey: "integration:test-gateway",
|
|
942
|
+
createdAt: 0,
|
|
943
|
+
updatedAt: 0,
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
mockOrchestrateOAuthConnect = async () => ({
|
|
947
|
+
success: false,
|
|
948
|
+
error:
|
|
949
|
+
"oauth2_connect from a non-interactive session requires a public ingress URL. Configure ingress.publicBaseUrl first.",
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
const { exitCode, stdout } = await runCli([
|
|
953
|
+
"connections",
|
|
954
|
+
"connect",
|
|
955
|
+
"integration:test-gateway",
|
|
956
|
+
"--json",
|
|
957
|
+
]);
|
|
958
|
+
expect(exitCode).toBe(1);
|
|
959
|
+
const parsed = JSON.parse(stdout);
|
|
960
|
+
expect(parsed.ok).toBe(false);
|
|
961
|
+
expect(parsed.error).toContain("requires a public ingress URL");
|
|
962
|
+
});
|
|
963
|
+
|
|
843
964
|
test("fails when client_secret is required but missing", async () => {
|
|
965
|
+
mockGetAppByProviderAndClientId = () => ({
|
|
966
|
+
id: "app-1",
|
|
967
|
+
clientId: "test-id",
|
|
968
|
+
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
969
|
+
providerKey: "integration:google",
|
|
970
|
+
createdAt: 0,
|
|
971
|
+
updatedAt: 0,
|
|
972
|
+
});
|
|
844
973
|
mockGetProviderBehavior = () => ({
|
|
845
974
|
setup: {
|
|
846
975
|
requiresClientSecret: true,
|
|
@@ -853,7 +982,7 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
853
982
|
const { exitCode, stdout } = await runCli([
|
|
854
983
|
"connections",
|
|
855
984
|
"connect",
|
|
856
|
-
"integration:
|
|
985
|
+
"integration:google",
|
|
857
986
|
"--client-id",
|
|
858
987
|
"test-id",
|
|
859
988
|
"--json",
|
|
@@ -881,7 +1010,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
881
1010
|
mockUpsertAppCalls = [];
|
|
882
1011
|
mockUpsertAppResult = {
|
|
883
1012
|
id: "app-upsert-1",
|
|
884
|
-
providerKey: "integration:
|
|
1013
|
+
providerKey: "integration:google",
|
|
885
1014
|
clientId: "abc123",
|
|
886
1015
|
createdAt: 1700000000000,
|
|
887
1016
|
updatedAt: 1700000000000,
|
|
@@ -896,6 +1025,8 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
896
1025
|
mockGetProvider = () => undefined;
|
|
897
1026
|
mockGetProviderBehavior = () => undefined;
|
|
898
1027
|
mockGetSecureKey = () => undefined;
|
|
1028
|
+
mockGetCredentialMetadata = () => undefined;
|
|
1029
|
+
mockUpsertAppImpl = undefined;
|
|
899
1030
|
});
|
|
900
1031
|
|
|
901
1032
|
test("upsert with --client-secret-credential-path passes path to upsertApp", async () => {
|
|
@@ -903,7 +1034,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
903
1034
|
"apps",
|
|
904
1035
|
"upsert",
|
|
905
1036
|
"--provider",
|
|
906
|
-
"integration:
|
|
1037
|
+
"integration:google",
|
|
907
1038
|
"--client-id",
|
|
908
1039
|
"abc123",
|
|
909
1040
|
"--client-secret-credential-path",
|
|
@@ -913,7 +1044,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
913
1044
|
expect(exitCode).toBe(0);
|
|
914
1045
|
expect(mockUpsertAppCalls).toHaveLength(1);
|
|
915
1046
|
expect(mockUpsertAppCalls[0]).toEqual({
|
|
916
|
-
provider: "integration:
|
|
1047
|
+
provider: "integration:google",
|
|
917
1048
|
clientId: "abc123",
|
|
918
1049
|
clientSecretOpts: { clientSecretCredentialPath: "custom/path" },
|
|
919
1050
|
});
|
|
@@ -926,7 +1057,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
926
1057
|
"apps",
|
|
927
1058
|
"upsert",
|
|
928
1059
|
"--provider",
|
|
929
|
-
"integration:
|
|
1060
|
+
"integration:google",
|
|
930
1061
|
"--client-id",
|
|
931
1062
|
"abc123",
|
|
932
1063
|
"--client-secret",
|
|
@@ -950,7 +1081,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
950
1081
|
"apps",
|
|
951
1082
|
"upsert",
|
|
952
1083
|
"--provider",
|
|
953
|
-
"integration:
|
|
1084
|
+
"integration:google",
|
|
954
1085
|
"--client-id",
|
|
955
1086
|
"abc123",
|
|
956
1087
|
"--client-secret",
|
|
@@ -960,7 +1091,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
960
1091
|
expect(exitCode).toBe(0);
|
|
961
1092
|
expect(mockUpsertAppCalls).toHaveLength(1);
|
|
962
1093
|
expect(mockUpsertAppCalls[0]).toEqual({
|
|
963
|
-
provider: "integration:
|
|
1094
|
+
provider: "integration:google",
|
|
964
1095
|
clientId: "abc123",
|
|
965
1096
|
clientSecretOpts: { clientSecretValue: "s3cret" },
|
|
966
1097
|
});
|
|
@@ -971,7 +1102,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
971
1102
|
"apps",
|
|
972
1103
|
"upsert",
|
|
973
1104
|
"--provider",
|
|
974
|
-
"integration:
|
|
1105
|
+
"integration:google",
|
|
975
1106
|
"--client-id",
|
|
976
1107
|
"abc123",
|
|
977
1108
|
"--json",
|
|
@@ -979,11 +1110,105 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
979
1110
|
expect(exitCode).toBe(0);
|
|
980
1111
|
expect(mockUpsertAppCalls).toHaveLength(1);
|
|
981
1112
|
expect(mockUpsertAppCalls[0]).toEqual({
|
|
982
|
-
provider: "integration:
|
|
1113
|
+
provider: "integration:google",
|
|
983
1114
|
clientId: "abc123",
|
|
984
1115
|
clientSecretOpts: undefined,
|
|
985
1116
|
});
|
|
986
1117
|
});
|
|
1118
|
+
|
|
1119
|
+
test("upsert resolves non-prefixed credential path via metadata store", async () => {
|
|
1120
|
+
// The resolution logic splits on the LAST colon, so
|
|
1121
|
+
// "integration:google:client_secret" → service="integration:google", field="client_secret"
|
|
1122
|
+
mockGetCredentialMetadata = (service, field) =>
|
|
1123
|
+
service === "integration:google" && field === "client_secret"
|
|
1124
|
+
? {
|
|
1125
|
+
credentialId: "cred-1",
|
|
1126
|
+
service: "integration:google",
|
|
1127
|
+
field: "client_secret",
|
|
1128
|
+
allowedTools: [],
|
|
1129
|
+
allowedDomains: [],
|
|
1130
|
+
createdAt: Date.now(),
|
|
1131
|
+
updatedAt: Date.now(),
|
|
1132
|
+
}
|
|
1133
|
+
: undefined;
|
|
1134
|
+
|
|
1135
|
+
const { exitCode, stdout } = await runCli([
|
|
1136
|
+
"apps",
|
|
1137
|
+
"upsert",
|
|
1138
|
+
"--provider",
|
|
1139
|
+
"integration:google",
|
|
1140
|
+
"--client-id",
|
|
1141
|
+
"abc",
|
|
1142
|
+
"--client-secret-credential-path",
|
|
1143
|
+
"integration:google:client_secret",
|
|
1144
|
+
"--json",
|
|
1145
|
+
]);
|
|
1146
|
+
expect(exitCode).toBe(0);
|
|
1147
|
+
expect(mockUpsertAppCalls).toHaveLength(1);
|
|
1148
|
+
// The non-prefixed path should have been resolved to the full credential key
|
|
1149
|
+
expect(mockUpsertAppCalls[0]).toEqual({
|
|
1150
|
+
provider: "integration:google",
|
|
1151
|
+
clientId: "abc",
|
|
1152
|
+
clientSecretOpts: {
|
|
1153
|
+
clientSecretCredentialPath:
|
|
1154
|
+
"credential/integration:google/client_secret",
|
|
1155
|
+
},
|
|
1156
|
+
});
|
|
1157
|
+
const parsed = JSON.parse(stdout);
|
|
1158
|
+
expect(parsed.id).toBe("app-upsert-1");
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
test("upsert passes prefixed credential path through unchanged", async () => {
|
|
1162
|
+
const { exitCode, stdout } = await runCli([
|
|
1163
|
+
"apps",
|
|
1164
|
+
"upsert",
|
|
1165
|
+
"--provider",
|
|
1166
|
+
"integration:google",
|
|
1167
|
+
"--client-id",
|
|
1168
|
+
"abc",
|
|
1169
|
+
"--client-secret-credential-path",
|
|
1170
|
+
"credential/integration:google/client_secret",
|
|
1171
|
+
"--json",
|
|
1172
|
+
]);
|
|
1173
|
+
expect(exitCode).toBe(0);
|
|
1174
|
+
expect(mockUpsertAppCalls).toHaveLength(1);
|
|
1175
|
+
// Already-prefixed path should be passed through as-is
|
|
1176
|
+
expect(mockUpsertAppCalls[0]).toEqual({
|
|
1177
|
+
provider: "integration:google",
|
|
1178
|
+
clientId: "abc",
|
|
1179
|
+
clientSecretOpts: {
|
|
1180
|
+
clientSecretCredentialPath:
|
|
1181
|
+
"credential/integration:google/client_secret",
|
|
1182
|
+
},
|
|
1183
|
+
});
|
|
1184
|
+
const parsed = JSON.parse(stdout);
|
|
1185
|
+
expect(parsed.id).toBe("app-upsert-1");
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
test("upsert with invalid credential path returns error when no secret found", async () => {
|
|
1189
|
+
// Override upsertApp to throw when given an unresolvable credential path
|
|
1190
|
+
mockUpsertAppImpl = async (_provider, _clientId, clientSecretOpts) => {
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
`No secret found at credential path: ${clientSecretOpts?.clientSecretCredentialPath}`,
|
|
1193
|
+
);
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
const { exitCode, stdout } = await runCli([
|
|
1197
|
+
"apps",
|
|
1198
|
+
"upsert",
|
|
1199
|
+
"--provider",
|
|
1200
|
+
"integration:google",
|
|
1201
|
+
"--client-id",
|
|
1202
|
+
"abc",
|
|
1203
|
+
"--client-secret-credential-path",
|
|
1204
|
+
"bogus:nonexistent:path",
|
|
1205
|
+
"--json",
|
|
1206
|
+
]);
|
|
1207
|
+
expect(exitCode).toBe(1);
|
|
1208
|
+
const parsed = JSON.parse(stdout);
|
|
1209
|
+
expect(parsed.ok).toBe(false);
|
|
1210
|
+
expect(parsed.error).toContain("No secret found");
|
|
1211
|
+
});
|
|
987
1212
|
});
|
|
988
1213
|
|
|
989
1214
|
// ---------------------------------------------------------------------------
|
|
@@ -1002,7 +1227,7 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1002
1227
|
|
|
1003
1228
|
test("returns ok when ping endpoint returns 200", async () => {
|
|
1004
1229
|
mockGetProvider = () => ({
|
|
1005
|
-
providerKey: "integration:
|
|
1230
|
+
providerKey: "integration:google",
|
|
1006
1231
|
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
1007
1232
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
1008
1233
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
@@ -1019,7 +1244,7 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1019
1244
|
const { exitCode, stdout } = await runCli([
|
|
1020
1245
|
"connections",
|
|
1021
1246
|
"ping",
|
|
1022
|
-
"integration:
|
|
1247
|
+
"integration:google",
|
|
1023
1248
|
"--json",
|
|
1024
1249
|
]);
|
|
1025
1250
|
expect(exitCode).toBe(0);
|
|
@@ -1071,7 +1296,7 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1071
1296
|
|
|
1072
1297
|
test("exits 1 when ping endpoint returns non-2xx", async () => {
|
|
1073
1298
|
mockGetProvider = () => ({
|
|
1074
|
-
providerKey: "integration:
|
|
1299
|
+
providerKey: "integration:google",
|
|
1075
1300
|
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
1076
1301
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
1077
1302
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
@@ -1082,19 +1307,20 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1082
1307
|
updatedAt: Date.now(),
|
|
1083
1308
|
});
|
|
1084
1309
|
const originalFetch = globalThis.fetch;
|
|
1310
|
+
// Use 403 (not 401) — 401 now throws inside withValidToken for retry
|
|
1085
1311
|
globalThis.fetch = (async () =>
|
|
1086
|
-
new Response("
|
|
1312
|
+
new Response("Forbidden", { status: 403 })) as unknown as typeof fetch;
|
|
1087
1313
|
try {
|
|
1088
1314
|
const { exitCode, stdout } = await runCli([
|
|
1089
1315
|
"connections",
|
|
1090
1316
|
"ping",
|
|
1091
|
-
"integration:
|
|
1317
|
+
"integration:google",
|
|
1092
1318
|
"--json",
|
|
1093
1319
|
]);
|
|
1094
1320
|
expect(exitCode).toBe(1);
|
|
1095
1321
|
const parsed = JSON.parse(stdout);
|
|
1096
1322
|
expect(parsed.ok).toBe(false);
|
|
1097
|
-
expect(parsed.status).toBe(
|
|
1323
|
+
expect(parsed.status).toBe(403);
|
|
1098
1324
|
} finally {
|
|
1099
1325
|
globalThis.fetch = originalFetch;
|
|
1100
1326
|
}
|
|
@@ -1102,10 +1328,10 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1102
1328
|
|
|
1103
1329
|
test("exits 1 when no token exists", async () => {
|
|
1104
1330
|
mockWithValidToken = async () => {
|
|
1105
|
-
throw new Error('No access token found for "integration:
|
|
1331
|
+
throw new Error('No access token found for "integration:google".');
|
|
1106
1332
|
};
|
|
1107
1333
|
mockGetProvider = () => ({
|
|
1108
|
-
providerKey: "integration:
|
|
1334
|
+
providerKey: "integration:google",
|
|
1109
1335
|
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
1110
1336
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
1111
1337
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
@@ -1118,7 +1344,7 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1118
1344
|
const { exitCode, stdout } = await runCli([
|
|
1119
1345
|
"connections",
|
|
1120
1346
|
"ping",
|
|
1121
|
-
"integration:
|
|
1347
|
+
"integration:google",
|
|
1122
1348
|
"--json",
|
|
1123
1349
|
]);
|
|
1124
1350
|
expect(exitCode).toBe(1);
|