@vellumai/assistant 0.5.9 → 0.5.11
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/AGENTS.md +9 -1
- package/ARCHITECTURE.md +48 -48
- package/Dockerfile +2 -0
- package/README.md +1 -1
- package/docs/architecture/integrations.md +6 -13
- package/docs/architecture/memory.md +7 -12
- package/docs/architecture/security.md +5 -5
- package/docs/credential-execution-service.md +9 -9
- package/docs/skills.md +1 -1
- package/node_modules/@vellumai/credential-storage/src/index.ts +2 -2
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
- package/openapi.yaml +7130 -0
- package/package.json +2 -1
- package/scripts/generate-openapi.ts +562 -0
- package/src/__tests__/acp-session.test.ts +239 -44
- package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
- package/src/__tests__/browser-skill-endstate.test.ts +1 -1
- package/src/__tests__/btw-routes.test.ts +8 -0
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
- package/src/__tests__/channel-approvals.test.ts +7 -7
- package/src/__tests__/channel-readiness-service.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +10 -2
- package/src/__tests__/context-memory-e2e.test.ts +2 -6
- package/src/__tests__/conversation-skill-tools.test.ts +1 -3
- package/src/__tests__/conversation-title-service.test.ts +2 -15
- package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
- package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
- package/src/__tests__/credential-security-e2e.test.ts +4 -4
- package/src/__tests__/credential-security-invariants.test.ts +3 -3
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/heartbeat-service.test.ts +35 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
- package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-recall-log-store.test.ts +182 -0
- package/src/__tests__/memory-recall-quality.test.ts +6 -8
- package/src/__tests__/memory-regressions.test.ts +53 -42
- package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
- package/src/__tests__/messaging-skill-split.test.ts +2 -17
- package/src/__tests__/oauth-cli.test.ts +98 -551
- package/src/__tests__/platform-callback-registration.test.ts +119 -0
- package/src/__tests__/secret-ingress-channel.test.ts +261 -0
- package/src/__tests__/secret-ingress-cli.test.ts +201 -0
- package/src/__tests__/secret-ingress-http.test.ts +312 -0
- package/src/__tests__/secret-ingress.test.ts +283 -0
- package/src/__tests__/secret-onetime-send.test.ts +4 -4
- package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
- package/src/__tests__/skill-feature-flags.test.ts +11 -19
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
- package/src/__tests__/skill-load-inline-command.test.ts +3 -3
- package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
- package/src/__tests__/skill-memory.test.ts +2 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
- package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
- package/src/__tests__/skills.test.ts +16 -2
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-skill.test.ts +5 -69
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +5 -238
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -206
- package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +15 -7
- package/src/acp/client-handler.ts +113 -31
- package/src/acp/session-manager.ts +29 -27
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/cli/AGENTS.md +73 -0
- package/src/cli/commands/autonomy.ts +3 -5
- package/src/cli/commands/credential-execution.ts +1 -2
- package/src/cli/commands/credentials.ts +4 -4
- package/src/cli/commands/memory.ts +2 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +785 -0
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +760 -0
- package/src/cli/commands/oauth/__tests__/mode.test.ts +672 -0
- package/src/cli/commands/oauth/__tests__/ping.test.ts +690 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +579 -0
- package/src/cli/commands/oauth/__tests__/token.test.ts +467 -0
- package/src/cli/commands/oauth/apps.ts +29 -11
- package/src/cli/commands/oauth/connect.ts +373 -0
- package/src/cli/commands/oauth/connections.ts +14 -493
- package/src/cli/commands/oauth/disconnect.ts +333 -0
- package/src/cli/commands/oauth/index.ts +62 -10
- package/src/cli/commands/oauth/mode.ts +263 -0
- package/src/cli/commands/oauth/ping.ts +222 -0
- package/src/cli/commands/oauth/providers.ts +30 -3
- package/src/cli/commands/oauth/request.ts +576 -0
- package/src/cli/commands/oauth/shared.ts +132 -0
- package/src/cli/commands/oauth/status.ts +202 -0
- package/src/cli/commands/oauth/token.ts +159 -0
- package/src/cli/commands/platform.ts +20 -14
- package/src/cli.ts +82 -17
- package/src/config/assistant-feature-flags.ts +74 -11
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +13 -36
- package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +2 -2
- package/src/config/bundled-skills/settings/SKILL.md +5 -3
- package/src/config/bundled-skills/settings/TOOLS.json +17 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
- package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
- package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
- package/src/config/bundled-skills/slack/SKILL.md +58 -44
- package/src/config/bundled-tool-registry.ts +2 -19
- package/src/config/env.ts +5 -1
- package/src/config/feature-flag-registry.json +57 -41
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/security.ts +9 -1
- package/src/config/schemas/services.ts +1 -1
- package/src/config/skill-state.ts +1 -3
- package/src/config/skills.ts +2 -4
- package/src/credential-execution/feature-gates.ts +9 -16
- package/src/credential-execution/process-manager.ts +12 -0
- package/src/daemon/config-watcher.ts +4 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
- package/src/daemon/conversation-agent-loop.ts +49 -2
- package/src/daemon/conversation-memory.ts +0 -1
- package/src/daemon/handlers/config-slack-channel.ts +43 -1
- package/src/daemon/handlers/conversations.ts +41 -33
- package/src/daemon/lifecycle.ts +28 -5
- package/src/daemon/message-types/acp.ts +0 -15
- package/src/daemon/message-types/memory.ts +0 -1
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +9 -0
- package/src/daemon/server.ts +19 -7
- package/src/email/feature-gate.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +48 -0
- package/src/inbound/platform-callback-registration.ts +61 -7
- package/src/mcp/mcp-oauth-provider.ts +3 -3
- package/src/memory/app-store.ts +3 -3
- package/src/memory/conversation-crud.ts +124 -0
- package/src/memory/conversation-title-service.ts +7 -17
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-local.ts +47 -2
- package/src/memory/indexer.ts +13 -10
- package/src/memory/items-extractor.ts +12 -4
- package/src/memory/job-utils.ts +5 -0
- package/src/memory/jobs-store.ts +10 -2
- package/src/memory/journal-memory.ts +6 -2
- package/src/memory/llm-request-log-store.ts +88 -21
- package/src/memory/memory-recall-log-store.ts +128 -0
- package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
- package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/migrations/validate-migration-state.ts +14 -1
- package/src/memory/retriever.test.ts +4 -5
- package/src/memory/schema/infrastructure.ts +31 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -1
- package/src/oauth/connect-orchestrator.ts +54 -0
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +26 -5
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -2
- package/src/permissions/trust-client.ts +2 -2
- package/src/platform/client.ts +2 -2
- package/src/prompts/journal-context.ts +6 -1
- package/src/providers/anthropic/client.ts +143 -1
- package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
- package/src/runtime/auth/route-policy.ts +0 -1
- package/src/runtime/btw-sidechain.ts +7 -1
- package/src/runtime/channel-approvals.ts +2 -2
- package/src/runtime/channel-readiness-service.ts +30 -7
- package/src/runtime/http-router.ts +31 -0
- package/src/runtime/http-server.ts +21 -4
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/pending-interactions.ts +21 -3
- package/src/runtime/routes/acp-routes.ts +46 -28
- package/src/runtime/routes/app-management-routes.ts +123 -0
- package/src/runtime/routes/app-routes.ts +31 -0
- package/src/runtime/routes/approval-routes.ts +108 -3
- package/src/runtime/routes/attachment-routes.ts +45 -0
- package/src/runtime/routes/avatar-routes.ts +16 -0
- package/src/runtime/routes/brain-graph-routes.ts +18 -0
- package/src/runtime/routes/btw-routes.ts +20 -0
- package/src/runtime/routes/call-routes.ts +81 -0
- package/src/runtime/routes/channel-readiness-routes.ts +48 -7
- package/src/runtime/routes/channel-routes.ts +18 -0
- package/src/runtime/routes/channel-verification-routes.ts +49 -1
- package/src/runtime/routes/contact-routes.ts +77 -0
- package/src/runtime/routes/conversation-attention-routes.ts +37 -0
- package/src/runtime/routes/conversation-management-routes.ts +94 -0
- package/src/runtime/routes/conversation-query-routes.ts +78 -0
- package/src/runtime/routes/conversation-routes.ts +115 -38
- package/src/runtime/routes/conversation-starter-routes.ts +29 -0
- package/src/runtime/routes/debug-routes.ts +23 -0
- package/src/runtime/routes/diagnostics-routes.ts +30 -0
- package/src/runtime/routes/documents-routes.ts +42 -0
- package/src/runtime/routes/events-routes.ts +10 -0
- package/src/runtime/routes/global-search-routes.ts +35 -0
- package/src/runtime/routes/guardian-action-routes.ts +47 -2
- package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
- package/src/runtime/routes/heartbeat-routes.ts +278 -0
- package/src/runtime/routes/host-bash-routes.ts +16 -1
- package/src/runtime/routes/host-cu-routes.ts +23 -1
- package/src/runtime/routes/host-file-routes.ts +18 -1
- package/src/runtime/routes/identity-routes.ts +35 -0
- package/src/runtime/routes/inbound-message-handler.ts +46 -25
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
- package/src/runtime/routes/integrations/twilio.ts +32 -22
- package/src/runtime/routes/invite-routes.ts +83 -0
- package/src/runtime/routes/log-export-routes.ts +14 -0
- package/src/runtime/routes/memory-item-routes.ts +99 -1
- package/src/runtime/routes/migration-rollback-routes.ts +25 -0
- package/src/runtime/routes/migration-routes.ts +40 -0
- package/src/runtime/routes/notification-routes.ts +20 -0
- package/src/runtime/routes/oauth-apps.ts +11 -3
- package/src/runtime/routes/pairing-routes.ts +15 -0
- package/src/runtime/routes/recording-routes.ts +72 -0
- package/src/runtime/routes/schedule-routes.ts +77 -5
- package/src/runtime/routes/secret-routes.ts +63 -1
- package/src/runtime/routes/settings-routes.ts +91 -1
- package/src/runtime/routes/skills-routes.ts +98 -16
- package/src/runtime/routes/subagents-routes.ts +38 -3
- package/src/runtime/routes/surface-action-routes.ts +66 -24
- package/src/runtime/routes/surface-content-routes.ts +20 -0
- package/src/runtime/routes/telemetry-routes.ts +12 -0
- package/src/runtime/routes/trace-event-routes.ts +25 -0
- package/src/runtime/routes/trust-rules-routes.ts +46 -0
- package/src/runtime/routes/tts-routes.ts +15 -4
- package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
- package/src/runtime/routes/usage-routes.ts +59 -0
- package/src/runtime/routes/watch-routes.ts +28 -0
- package/src/runtime/routes/work-items-routes.ts +59 -0
- package/src/runtime/routes/workspace-commit-routes.ts +12 -0
- package/src/runtime/routes/workspace-routes.ts +102 -0
- package/src/schedule/scheduler.ts +7 -1
- package/src/security/AGENTS.md +7 -0
- package/src/security/credential-backend.ts +1 -1
- package/src/security/encrypted-store.ts +3 -3
- package/src/security/oauth2.ts +55 -0
- package/src/security/secret-ingress.ts +174 -0
- package/src/security/secret-patterns.ts +133 -0
- package/src/security/secret-scanner.ts +28 -117
- package/src/signals/confirm.ts +12 -8
- package/src/signals/user-message.ts +18 -3
- package/src/skills/skill-memory.ts +1 -2
- package/src/tasks/task-runner.ts +7 -1
- package/src/tools/credentials/broker.ts +1 -1
- package/src/tools/credentials/metadata-store.ts +1 -1
- package/src/tools/credentials/vault.ts +2 -3
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +2 -4
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/tool-manifest.ts +1 -1
- package/src/util/log-redact.ts +9 -34
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +13 -148
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +7 -145
- package/src/workspace/migrations/AGENTS.md +11 -0
- package/src/workspace/migrations/runner.ts +16 -6
- package/src/workspace/migrations/types.ts +7 -0
- package/docs/architecture/keychain-broker.md +0 -69
- package/src/__tests__/keychain-broker-client.test.ts +0 -800
- package/src/cli/commands/oauth/platform.ts +0 -525
- package/src/config/bundled-skills/slack/TOOLS.json +0 -272
- package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
- package/src/security/keychain-broker-client.ts +0 -446
|
@@ -2,8 +2,6 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
|
|
5
|
-
import { credentialKey } from "../security/credential-key.js";
|
|
6
|
-
|
|
7
5
|
// ---------------------------------------------------------------------------
|
|
8
6
|
// Mock state
|
|
9
7
|
// ---------------------------------------------------------------------------
|
|
@@ -13,10 +11,9 @@ let mockWithValidToken: <T>(
|
|
|
13
11
|
cb: (token: string) => Promise<T>,
|
|
14
12
|
) => Promise<T>;
|
|
15
13
|
|
|
16
|
-
// Disconnect mock state
|
|
17
14
|
let mockListProviders: () => Array<Record<string, unknown>> = () => [];
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const secureKeyStore = new Map<string, string>();
|
|
16
|
+
const metadataStore: Array<{
|
|
20
17
|
credentialId: string;
|
|
21
18
|
service: string;
|
|
22
19
|
field: string;
|
|
@@ -25,10 +22,9 @@ let metadataStore: Array<{
|
|
|
25
22
|
createdAt: number;
|
|
26
23
|
updatedAt: number;
|
|
27
24
|
}> = [];
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const disconnectOAuthProviderCalls: string[] = [];
|
|
26
|
+
const disconnectOAuthProviderResult: "disconnected" | "not-found" | "error" =
|
|
30
27
|
"not-found";
|
|
31
|
-
let idCounter = 0;
|
|
32
28
|
|
|
33
29
|
// App upsert mock state
|
|
34
30
|
let mockUpsertAppCalls: Array<{
|
|
@@ -57,7 +53,7 @@ let mockUpsertAppImpl:
|
|
|
57
53
|
) => Promise<Record<string, unknown>>)
|
|
58
54
|
| undefined;
|
|
59
55
|
|
|
60
|
-
//
|
|
56
|
+
// Transitive mock state (connect-orchestrator, provider-behaviors, etc.)
|
|
61
57
|
let mockOrchestrateOAuthConnect: (
|
|
62
58
|
opts: Record<string, unknown>,
|
|
63
59
|
) => Promise<Record<string, unknown>>;
|
|
@@ -75,16 +71,27 @@ let mockGetProviderBehavior: (
|
|
|
75
71
|
providerKey: string,
|
|
76
72
|
) => Record<string, unknown> | undefined = () => undefined;
|
|
77
73
|
let mockGetSecureKey: (account: string) => string | undefined = () => undefined;
|
|
74
|
+
let mockResolveOAuthConnection: (
|
|
75
|
+
providerKey: string,
|
|
76
|
+
options?: Record<string, unknown>,
|
|
77
|
+
) => Promise<{
|
|
78
|
+
request: (req: Record<string, unknown>) => Promise<{
|
|
79
|
+
status: number;
|
|
80
|
+
headers: Record<string, string>;
|
|
81
|
+
body: unknown;
|
|
82
|
+
}>;
|
|
83
|
+
withToken: <T>(fn: (token: string) => Promise<T>) => Promise<T>;
|
|
84
|
+
id: string;
|
|
85
|
+
providerKey: string;
|
|
86
|
+
accountInfo: string | null;
|
|
87
|
+
}> = async () => {
|
|
88
|
+
throw new Error("resolveOAuthConnection not configured in test");
|
|
89
|
+
};
|
|
78
90
|
let mockGetCredentialMetadata: (
|
|
79
91
|
service: string,
|
|
80
92
|
field: string,
|
|
81
93
|
) => Record<string, unknown> | undefined = () => undefined;
|
|
82
94
|
|
|
83
|
-
function nextUUID(): string {
|
|
84
|
-
idCounter += 1;
|
|
85
|
-
return `00000000-0000-0000-0000-${String(idCounter).padStart(12, "0")}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
95
|
// ---------------------------------------------------------------------------
|
|
89
96
|
// Mock token-manager
|
|
90
97
|
// ---------------------------------------------------------------------------
|
|
@@ -107,7 +114,7 @@ mock.module("../security/token-manager.js", () => ({
|
|
|
107
114
|
}));
|
|
108
115
|
|
|
109
116
|
// ---------------------------------------------------------------------------
|
|
110
|
-
// Mock oauth-store
|
|
117
|
+
// Mock oauth-store
|
|
111
118
|
// ---------------------------------------------------------------------------
|
|
112
119
|
|
|
113
120
|
mock.module("../oauth/oauth-store.js", () => ({
|
|
@@ -147,6 +154,8 @@ mock.module("../oauth/oauth-store.js", () => ({
|
|
|
147
154
|
listProviders: () => mockListProviders(),
|
|
148
155
|
registerProvider: () => ({}),
|
|
149
156
|
seedProviders: () => {},
|
|
157
|
+
getActiveConnection: () => undefined,
|
|
158
|
+
listActiveConnectionsByProvider: () => [],
|
|
150
159
|
createConnection: () => ({}),
|
|
151
160
|
isProviderConnected: () => false,
|
|
152
161
|
updateConnection: () => ({}),
|
|
@@ -167,7 +176,10 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
167
176
|
}
|
|
168
177
|
return "not-found" as const;
|
|
169
178
|
},
|
|
170
|
-
listSecureKeysAsync: async () => ({
|
|
179
|
+
listSecureKeysAsync: async () => ({
|
|
180
|
+
accounts: [...secureKeyStore.keys()],
|
|
181
|
+
unreachable: false,
|
|
182
|
+
}),
|
|
171
183
|
_resetBackend: () => {},
|
|
172
184
|
}));
|
|
173
185
|
|
|
@@ -206,6 +218,27 @@ mock.module("../oauth/provider-behaviors.js", () => ({
|
|
|
206
218
|
mockGetProviderBehavior(providerKey),
|
|
207
219
|
}));
|
|
208
220
|
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Mock connection-resolver (needed by request.ts)
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
mock.module("../oauth/connection-resolver.js", () => ({
|
|
226
|
+
resolveOAuthConnection: (
|
|
227
|
+
providerKey: string,
|
|
228
|
+
options?: Record<string, unknown>,
|
|
229
|
+
) => mockResolveOAuthConnection(providerKey, options),
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Mock platform/client (needed by request.ts)
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
mock.module("../platform/client.js", () => ({
|
|
237
|
+
VellumPlatformClient: {
|
|
238
|
+
create: async () => null,
|
|
239
|
+
},
|
|
240
|
+
}));
|
|
241
|
+
|
|
209
242
|
mock.module("../util/logger.js", () => ({
|
|
210
243
|
getLogger: () => ({
|
|
211
244
|
info: () => {},
|
|
@@ -276,29 +309,19 @@ async function runCli(
|
|
|
276
309
|
// Tests
|
|
277
310
|
// ---------------------------------------------------------------------------
|
|
278
311
|
|
|
279
|
-
describe("assistant oauth
|
|
312
|
+
describe("assistant oauth token <provider-key>", () => {
|
|
280
313
|
beforeEach(() => {
|
|
281
314
|
mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
|
|
282
|
-
secureKeyStore = new Map();
|
|
283
|
-
metadataStore = [];
|
|
284
|
-
disconnectOAuthProviderCalls = [];
|
|
285
|
-
disconnectOAuthProviderResult = "not-found";
|
|
286
|
-
idCounter = 0;
|
|
287
315
|
});
|
|
288
316
|
|
|
289
317
|
test("prints bare token in human mode", async () => {
|
|
290
|
-
const { exitCode, stdout } = await runCli([
|
|
291
|
-
"connections",
|
|
292
|
-
"token",
|
|
293
|
-
"integration:twitter",
|
|
294
|
-
]);
|
|
318
|
+
const { exitCode, stdout } = await runCli(["token", "integration:twitter"]);
|
|
295
319
|
expect(exitCode).toBe(0);
|
|
296
320
|
expect(stdout).toBe("mock-access-token-xyz\n");
|
|
297
321
|
});
|
|
298
322
|
|
|
299
323
|
test("prints JSON in --json mode", async () => {
|
|
300
324
|
const { exitCode, stdout } = await runCli([
|
|
301
|
-
"connections",
|
|
302
325
|
"token",
|
|
303
326
|
"integration:twitter",
|
|
304
327
|
"--json",
|
|
@@ -315,7 +338,7 @@ describe("assistant oauth connections token <provider-key>", () => {
|
|
|
315
338
|
return cb("tok");
|
|
316
339
|
};
|
|
317
340
|
|
|
318
|
-
await runCli(["
|
|
341
|
+
await runCli(["token", "integration:twitter"]);
|
|
319
342
|
expect(capturedService).toBe("integration:twitter");
|
|
320
343
|
});
|
|
321
344
|
|
|
@@ -326,11 +349,7 @@ describe("assistant oauth connections token <provider-key>", () => {
|
|
|
326
349
|
return cb("gmail-token");
|
|
327
350
|
};
|
|
328
351
|
|
|
329
|
-
const { exitCode, stdout } = await runCli([
|
|
330
|
-
"connections",
|
|
331
|
-
"token",
|
|
332
|
-
"integration:google",
|
|
333
|
-
]);
|
|
352
|
+
const { exitCode, stdout } = await runCli(["token", "integration:google"]);
|
|
334
353
|
expect(exitCode).toBe(0);
|
|
335
354
|
expect(stdout).toBe("gmail-token\n");
|
|
336
355
|
expect(capturedService).toBe("integration:google");
|
|
@@ -344,7 +363,6 @@ describe("assistant oauth connections token <provider-key>", () => {
|
|
|
344
363
|
};
|
|
345
364
|
|
|
346
365
|
const { exitCode, stdout } = await runCli([
|
|
347
|
-
"connections",
|
|
348
366
|
"token",
|
|
349
367
|
"integration:twitter",
|
|
350
368
|
"--json",
|
|
@@ -363,7 +381,6 @@ describe("assistant oauth connections token <provider-key>", () => {
|
|
|
363
381
|
};
|
|
364
382
|
|
|
365
383
|
const { exitCode, stdout } = await runCli([
|
|
366
|
-
"connections",
|
|
367
384
|
"token",
|
|
368
385
|
"integration:twitter",
|
|
369
386
|
"--json",
|
|
@@ -378,152 +395,17 @@ describe("assistant oauth connections token <provider-key>", () => {
|
|
|
378
395
|
// Simulate withValidToken refreshing and returning a new token
|
|
379
396
|
mockWithValidToken = async (_service, cb) => cb("refreshed-new-token");
|
|
380
397
|
|
|
381
|
-
const { exitCode, stdout } = await runCli([
|
|
382
|
-
"connections",
|
|
383
|
-
"token",
|
|
384
|
-
"integration:twitter",
|
|
385
|
-
]);
|
|
398
|
+
const { exitCode, stdout } = await runCli(["token", "integration:twitter"]);
|
|
386
399
|
expect(exitCode).toBe(0);
|
|
387
400
|
expect(stdout).toBe("refreshed-new-token\n");
|
|
388
401
|
});
|
|
389
402
|
|
|
390
403
|
test("missing provider-key argument exits non-zero", async () => {
|
|
391
|
-
const { exitCode } = await runCli(["
|
|
404
|
+
const { exitCode } = await runCli(["token"]);
|
|
392
405
|
expect(exitCode).not.toBe(0);
|
|
393
406
|
});
|
|
394
407
|
});
|
|
395
408
|
|
|
396
|
-
// ---------------------------------------------------------------------------
|
|
397
|
-
// disconnect
|
|
398
|
-
// ---------------------------------------------------------------------------
|
|
399
|
-
|
|
400
|
-
describe("assistant oauth connections disconnect <provider-key>", () => {
|
|
401
|
-
beforeEach(() => {
|
|
402
|
-
mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
|
|
403
|
-
secureKeyStore = new Map();
|
|
404
|
-
metadataStore = [];
|
|
405
|
-
disconnectOAuthProviderCalls = [];
|
|
406
|
-
disconnectOAuthProviderResult = "not-found";
|
|
407
|
-
idCounter = 0;
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test("succeeds when an OAuth connection exists", async () => {
|
|
411
|
-
disconnectOAuthProviderResult = "disconnected";
|
|
412
|
-
|
|
413
|
-
const result = await runCli([
|
|
414
|
-
"connections",
|
|
415
|
-
"disconnect",
|
|
416
|
-
"integration:google",
|
|
417
|
-
"--json",
|
|
418
|
-
]);
|
|
419
|
-
expect(result.exitCode).toBe(0);
|
|
420
|
-
const parsed = JSON.parse(result.stdout);
|
|
421
|
-
expect(parsed.ok).toBe(true);
|
|
422
|
-
expect(parsed.service).toBe("integration:google");
|
|
423
|
-
|
|
424
|
-
// disconnectOAuthProvider should have been called with the full provider key
|
|
425
|
-
expect(disconnectOAuthProviderCalls).toEqual(["integration:google"]);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
test("reports not-found when nothing exists", async () => {
|
|
429
|
-
const result = await runCli([
|
|
430
|
-
"connections",
|
|
431
|
-
"disconnect",
|
|
432
|
-
"integration:google",
|
|
433
|
-
"--json",
|
|
434
|
-
]);
|
|
435
|
-
expect(result.exitCode).toBe(1);
|
|
436
|
-
const parsed = JSON.parse(result.stdout);
|
|
437
|
-
expect(parsed.ok).toBe(false);
|
|
438
|
-
expect(parsed.error).toContain("No OAuth connection or credentials");
|
|
439
|
-
expect(parsed.error).toContain("integration:google");
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
test("cleans up legacy credential keys if present", async () => {
|
|
443
|
-
// Seed legacy credential keys (no OAuth connection)
|
|
444
|
-
const legacyFields = [
|
|
445
|
-
"access_token",
|
|
446
|
-
"refresh_token",
|
|
447
|
-
"client_id",
|
|
448
|
-
"client_secret",
|
|
449
|
-
];
|
|
450
|
-
for (const field of legacyFields) {
|
|
451
|
-
secureKeyStore.set(
|
|
452
|
-
credentialKey("integration:google", field),
|
|
453
|
-
`legacy_${field}_value`,
|
|
454
|
-
);
|
|
455
|
-
metadataStore.push({
|
|
456
|
-
credentialId: nextUUID(),
|
|
457
|
-
service: "integration:google",
|
|
458
|
-
field,
|
|
459
|
-
allowedTools: [],
|
|
460
|
-
allowedDomains: [],
|
|
461
|
-
createdAt: Date.now(),
|
|
462
|
-
updatedAt: Date.now(),
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const result = await runCli([
|
|
467
|
-
"connections",
|
|
468
|
-
"disconnect",
|
|
469
|
-
"integration:google",
|
|
470
|
-
"--json",
|
|
471
|
-
]);
|
|
472
|
-
expect(result.exitCode).toBe(0);
|
|
473
|
-
const parsed = JSON.parse(result.stdout);
|
|
474
|
-
expect(parsed.ok).toBe(true);
|
|
475
|
-
expect(parsed.service).toBe("integration:google");
|
|
476
|
-
|
|
477
|
-
// All legacy keys should be removed
|
|
478
|
-
for (const field of legacyFields) {
|
|
479
|
-
expect(
|
|
480
|
-
secureKeyStore.has(credentialKey("integration:google", field)),
|
|
481
|
-
).toBe(false);
|
|
482
|
-
expect(
|
|
483
|
-
metadataStore.find(
|
|
484
|
-
(m) => m.service === "integration:google" && m.field === field,
|
|
485
|
-
),
|
|
486
|
-
).toBeUndefined();
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
test("cleans up both OAuth connection and legacy keys when both exist", async () => {
|
|
491
|
-
// Seed OAuth connection
|
|
492
|
-
disconnectOAuthProviderResult = "disconnected";
|
|
493
|
-
|
|
494
|
-
// Seed a legacy credential key
|
|
495
|
-
secureKeyStore.set(
|
|
496
|
-
credentialKey("integration:google", "access_token"),
|
|
497
|
-
"legacy_token",
|
|
498
|
-
);
|
|
499
|
-
metadataStore.push({
|
|
500
|
-
credentialId: nextUUID(),
|
|
501
|
-
service: "integration:google",
|
|
502
|
-
field: "access_token",
|
|
503
|
-
allowedTools: [],
|
|
504
|
-
allowedDomains: [],
|
|
505
|
-
createdAt: Date.now(),
|
|
506
|
-
updatedAt: Date.now(),
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
const result = await runCli([
|
|
510
|
-
"connections",
|
|
511
|
-
"disconnect",
|
|
512
|
-
"integration:google",
|
|
513
|
-
"--json",
|
|
514
|
-
]);
|
|
515
|
-
expect(result.exitCode).toBe(0);
|
|
516
|
-
const parsed = JSON.parse(result.stdout);
|
|
517
|
-
expect(parsed.ok).toBe(true);
|
|
518
|
-
|
|
519
|
-
// Both should be cleaned up
|
|
520
|
-
expect(disconnectOAuthProviderCalls).toEqual(["integration:google"]);
|
|
521
|
-
expect(
|
|
522
|
-
secureKeyStore.has(credentialKey("integration:google", "access_token")),
|
|
523
|
-
).toBe(false);
|
|
524
|
-
});
|
|
525
|
-
});
|
|
526
|
-
|
|
527
409
|
// ---------------------------------------------------------------------------
|
|
528
410
|
// providers list
|
|
529
411
|
// ---------------------------------------------------------------------------
|
|
@@ -575,11 +457,6 @@ describe("assistant oauth providers list", () => {
|
|
|
575
457
|
beforeEach(() => {
|
|
576
458
|
mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
|
|
577
459
|
mockListProviders = () => fakeProviders;
|
|
578
|
-
secureKeyStore = new Map();
|
|
579
|
-
metadataStore = [];
|
|
580
|
-
disconnectOAuthProviderCalls = [];
|
|
581
|
-
disconnectOAuthProviderResult = "not-found";
|
|
582
|
-
idCounter = 0;
|
|
583
460
|
});
|
|
584
461
|
|
|
585
462
|
test("returns all providers when no --provider-key is given", async () => {
|
|
@@ -673,320 +550,6 @@ describe("assistant oauth providers list", () => {
|
|
|
673
550
|
});
|
|
674
551
|
});
|
|
675
552
|
|
|
676
|
-
// ---------------------------------------------------------------------------
|
|
677
|
-
// connect
|
|
678
|
-
// ---------------------------------------------------------------------------
|
|
679
|
-
|
|
680
|
-
describe("assistant oauth connections connect <provider-key>", () => {
|
|
681
|
-
beforeEach(() => {
|
|
682
|
-
mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
|
|
683
|
-
secureKeyStore = new Map();
|
|
684
|
-
metadataStore = [];
|
|
685
|
-
disconnectOAuthProviderCalls = [];
|
|
686
|
-
disconnectOAuthProviderResult = "not-found";
|
|
687
|
-
idCounter = 0;
|
|
688
|
-
mockOrchestrateOAuthConnect = async () => ({
|
|
689
|
-
success: true,
|
|
690
|
-
deferred: false,
|
|
691
|
-
grantedScopes: [],
|
|
692
|
-
});
|
|
693
|
-
mockGetAppByProviderAndClientId = () => undefined;
|
|
694
|
-
mockGetMostRecentAppByProvider = () => undefined;
|
|
695
|
-
mockGetProvider = () => undefined;
|
|
696
|
-
mockGetProviderBehavior = () => undefined;
|
|
697
|
-
mockGetSecureKey = () => undefined;
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
test("completes interactive flow and prints success (human mode)", async () => {
|
|
701
|
-
mockGetAppByProviderAndClientId = () => ({
|
|
702
|
-
id: "app-1",
|
|
703
|
-
clientId: "test-id",
|
|
704
|
-
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
705
|
-
providerKey: "integration:google",
|
|
706
|
-
createdAt: 0,
|
|
707
|
-
updatedAt: 0,
|
|
708
|
-
});
|
|
709
|
-
mockOrchestrateOAuthConnect = async () => ({
|
|
710
|
-
success: true,
|
|
711
|
-
deferred: false,
|
|
712
|
-
grantedScopes: ["read"],
|
|
713
|
-
accountInfo: "user@example.com",
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
const { exitCode, stdout } = await runCli([
|
|
717
|
-
"connections",
|
|
718
|
-
"connect",
|
|
719
|
-
"integration:google",
|
|
720
|
-
"--client-id",
|
|
721
|
-
"test-id",
|
|
722
|
-
]);
|
|
723
|
-
expect(exitCode).toBe(0);
|
|
724
|
-
expect(stdout).toContain("Connected");
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
test("completes interactive flow and returns JSON with --json flag", async () => {
|
|
728
|
-
mockGetAppByProviderAndClientId = () => ({
|
|
729
|
-
id: "app-1",
|
|
730
|
-
clientId: "test-id",
|
|
731
|
-
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
732
|
-
providerKey: "integration:google",
|
|
733
|
-
createdAt: 0,
|
|
734
|
-
updatedAt: 0,
|
|
735
|
-
});
|
|
736
|
-
mockOrchestrateOAuthConnect = async () => ({
|
|
737
|
-
success: true,
|
|
738
|
-
deferred: false,
|
|
739
|
-
grantedScopes: ["read"],
|
|
740
|
-
accountInfo: "user@example.com",
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
const { exitCode, stdout } = await runCli([
|
|
744
|
-
"connections",
|
|
745
|
-
"connect",
|
|
746
|
-
"integration:google",
|
|
747
|
-
"--client-id",
|
|
748
|
-
"test-id",
|
|
749
|
-
"--json",
|
|
750
|
-
]);
|
|
751
|
-
expect(exitCode).toBe(0);
|
|
752
|
-
const parsed = JSON.parse(stdout);
|
|
753
|
-
expect(parsed).toEqual({
|
|
754
|
-
ok: true,
|
|
755
|
-
grantedScopes: ["read"],
|
|
756
|
-
accountInfo: "user@example.com",
|
|
757
|
-
});
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
test("returns auth URL in default (non-interactive) mode (JSON)", async () => {
|
|
761
|
-
mockGetAppByProviderAndClientId = () => ({
|
|
762
|
-
id: "app-1",
|
|
763
|
-
clientId: "test-id",
|
|
764
|
-
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
765
|
-
providerKey: "integration:google",
|
|
766
|
-
createdAt: 0,
|
|
767
|
-
updatedAt: 0,
|
|
768
|
-
});
|
|
769
|
-
mockOrchestrateOAuthConnect = async () => ({
|
|
770
|
-
success: true,
|
|
771
|
-
deferred: true,
|
|
772
|
-
authUrl: "https://example.com/auth",
|
|
773
|
-
state: "abc",
|
|
774
|
-
service: "integration:google",
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
const { exitCode, stdout } = await runCli([
|
|
778
|
-
"connections",
|
|
779
|
-
"connect",
|
|
780
|
-
"integration:google",
|
|
781
|
-
"--client-id",
|
|
782
|
-
"test-id",
|
|
783
|
-
"--json",
|
|
784
|
-
]);
|
|
785
|
-
expect(exitCode).toBe(0);
|
|
786
|
-
const parsed = JSON.parse(stdout);
|
|
787
|
-
expect(parsed.ok).toBe(true);
|
|
788
|
-
expect(parsed.deferred).toBe(true);
|
|
789
|
-
expect(parsed.authUrl).toBe("https://example.com/auth");
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
test("fails when no client_id available", async () => {
|
|
793
|
-
mockGetMostRecentAppByProvider = () => undefined;
|
|
794
|
-
|
|
795
|
-
const { exitCode, stdout } = await runCli([
|
|
796
|
-
"connections",
|
|
797
|
-
"connect",
|
|
798
|
-
"integration:google",
|
|
799
|
-
"--json",
|
|
800
|
-
]);
|
|
801
|
-
expect(exitCode).toBe(1);
|
|
802
|
-
const parsed = JSON.parse(stdout);
|
|
803
|
-
expect(parsed.ok).toBe(false);
|
|
804
|
-
expect(parsed.error).toContain("client_id");
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
test("resolves client_id from DB when not provided", async () => {
|
|
808
|
-
mockGetMostRecentAppByProvider = () => ({
|
|
809
|
-
id: "app-1",
|
|
810
|
-
clientId: "db-client-id",
|
|
811
|
-
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
812
|
-
providerKey: "integration:google",
|
|
813
|
-
createdAt: 0,
|
|
814
|
-
updatedAt: 0,
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
let capturedClientId: string | undefined;
|
|
818
|
-
mockOrchestrateOAuthConnect = async (opts) => {
|
|
819
|
-
capturedClientId = opts.clientId as string;
|
|
820
|
-
return {
|
|
821
|
-
success: true,
|
|
822
|
-
deferred: false,
|
|
823
|
-
grantedScopes: [],
|
|
824
|
-
};
|
|
825
|
-
};
|
|
826
|
-
|
|
827
|
-
await runCli(["connections", "connect", "integration:google"]);
|
|
828
|
-
expect(capturedClientId).toBe("db-client-id");
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
test("resolves client_secret from secure store when not provided", async () => {
|
|
832
|
-
mockGetMostRecentAppByProvider = () => ({
|
|
833
|
-
id: "app-1",
|
|
834
|
-
clientId: "db-client-id",
|
|
835
|
-
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
836
|
-
providerKey: "integration:google",
|
|
837
|
-
createdAt: 0,
|
|
838
|
-
updatedAt: 0,
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
mockGetSecureKey = (account: string) =>
|
|
842
|
-
account === "oauth_app/app-1/client_secret" ? "db-secret" : undefined;
|
|
843
|
-
|
|
844
|
-
let capturedOpts: Record<string, unknown> | undefined;
|
|
845
|
-
mockOrchestrateOAuthConnect = async (opts) => {
|
|
846
|
-
capturedOpts = opts;
|
|
847
|
-
return {
|
|
848
|
-
success: true,
|
|
849
|
-
deferred: false,
|
|
850
|
-
grantedScopes: [],
|
|
851
|
-
};
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
await runCli(["connections", "connect", "integration:google"]);
|
|
855
|
-
expect(capturedOpts).toBeDefined();
|
|
856
|
-
expect(capturedOpts!.clientId).toBe("db-client-id");
|
|
857
|
-
expect(capturedOpts!.clientSecret).toBe("db-secret");
|
|
858
|
-
});
|
|
859
|
-
|
|
860
|
-
test("outputs error from orchestrator", async () => {
|
|
861
|
-
mockGetAppByProviderAndClientId = () => ({
|
|
862
|
-
id: "app-1",
|
|
863
|
-
clientId: "x",
|
|
864
|
-
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
865
|
-
providerKey: "integration:google",
|
|
866
|
-
createdAt: 0,
|
|
867
|
-
updatedAt: 0,
|
|
868
|
-
});
|
|
869
|
-
mockOrchestrateOAuthConnect = async () => ({
|
|
870
|
-
success: false,
|
|
871
|
-
error: "Something went wrong",
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
const { exitCode, stdout } = await runCli([
|
|
875
|
-
"connections",
|
|
876
|
-
"connect",
|
|
877
|
-
"integration:google",
|
|
878
|
-
"--client-id",
|
|
879
|
-
"x",
|
|
880
|
-
"--json",
|
|
881
|
-
]);
|
|
882
|
-
expect(exitCode).toBe(1);
|
|
883
|
-
const parsed = JSON.parse(stdout);
|
|
884
|
-
expect(parsed.ok).toBe(false);
|
|
885
|
-
expect(parsed.error).toBe("Something went wrong");
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
test("succeeds when callbackTransport is null (loopback default)", async () => {
|
|
889
|
-
// Provider row has callbackTransport: null — orchestrator should default
|
|
890
|
-
// to loopback and not require a public ingress URL.
|
|
891
|
-
mockGetMostRecentAppByProvider = () => ({
|
|
892
|
-
id: "app-loopback",
|
|
893
|
-
clientId: "loopback-client",
|
|
894
|
-
clientSecretCredentialPath: "oauth_app/app-loopback/client_secret",
|
|
895
|
-
providerKey: "integration:test-loopback",
|
|
896
|
-
createdAt: 0,
|
|
897
|
-
updatedAt: 0,
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
let capturedOpts: Record<string, unknown> | undefined;
|
|
901
|
-
mockOrchestrateOAuthConnect = async (opts) => {
|
|
902
|
-
capturedOpts = opts;
|
|
903
|
-
return {
|
|
904
|
-
success: true,
|
|
905
|
-
deferred: true,
|
|
906
|
-
authUrl: "https://example.com/auth?loopback",
|
|
907
|
-
state: "state-loopback",
|
|
908
|
-
service: "integration:test-loopback",
|
|
909
|
-
};
|
|
910
|
-
};
|
|
911
|
-
|
|
912
|
-
const { exitCode, stdout } = await runCli([
|
|
913
|
-
"connections",
|
|
914
|
-
"connect",
|
|
915
|
-
"integration:test-loopback",
|
|
916
|
-
"--json",
|
|
917
|
-
]);
|
|
918
|
-
expect(exitCode).toBe(0);
|
|
919
|
-
const parsed = JSON.parse(stdout);
|
|
920
|
-
expect(parsed.ok).toBe(true);
|
|
921
|
-
expect(parsed.deferred).toBe(true);
|
|
922
|
-
expect(capturedOpts).toBeDefined();
|
|
923
|
-
expect(capturedOpts!.clientId).toBe("loopback-client");
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
test("returns ingress URL error when callbackTransport is explicitly gateway", async () => {
|
|
927
|
-
// Provider row has callbackTransport: "gateway" — orchestrator should
|
|
928
|
-
// require a public ingress URL, which is not configured in the test env.
|
|
929
|
-
mockGetMostRecentAppByProvider = () => ({
|
|
930
|
-
id: "app-gateway",
|
|
931
|
-
clientId: "gateway-client",
|
|
932
|
-
clientSecretCredentialPath: "oauth_app/app-gateway/client_secret",
|
|
933
|
-
providerKey: "integration:test-gateway",
|
|
934
|
-
createdAt: 0,
|
|
935
|
-
updatedAt: 0,
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
mockOrchestrateOAuthConnect = async () => ({
|
|
939
|
-
success: false,
|
|
940
|
-
error:
|
|
941
|
-
"oauth2_connect from a non-interactive session requires a public ingress URL. Configure ingress.publicBaseUrl first.",
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
const { exitCode, stdout } = await runCli([
|
|
945
|
-
"connections",
|
|
946
|
-
"connect",
|
|
947
|
-
"integration:test-gateway",
|
|
948
|
-
"--json",
|
|
949
|
-
]);
|
|
950
|
-
expect(exitCode).toBe(1);
|
|
951
|
-
const parsed = JSON.parse(stdout);
|
|
952
|
-
expect(parsed.ok).toBe(false);
|
|
953
|
-
expect(parsed.error).toContain("requires a public ingress URL");
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
test("fails when client_secret is required but missing", async () => {
|
|
957
|
-
mockGetAppByProviderAndClientId = () => ({
|
|
958
|
-
id: "app-1",
|
|
959
|
-
clientId: "test-id",
|
|
960
|
-
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
961
|
-
providerKey: "integration:google",
|
|
962
|
-
createdAt: 0,
|
|
963
|
-
updatedAt: 0,
|
|
964
|
-
});
|
|
965
|
-
mockGetProviderBehavior = () => ({
|
|
966
|
-
setup: {
|
|
967
|
-
requiresClientSecret: true,
|
|
968
|
-
displayName: "Test",
|
|
969
|
-
dashboardUrl: "https://example.com",
|
|
970
|
-
appType: "app",
|
|
971
|
-
},
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
const { exitCode, stdout } = await runCli([
|
|
975
|
-
"connections",
|
|
976
|
-
"connect",
|
|
977
|
-
"integration:google",
|
|
978
|
-
"--client-id",
|
|
979
|
-
"test-id",
|
|
980
|
-
"--json",
|
|
981
|
-
]);
|
|
982
|
-
expect(exitCode).toBe(1);
|
|
983
|
-
const parsed = JSON.parse(stdout);
|
|
984
|
-
expect(parsed.ok).toBe(false);
|
|
985
|
-
expect(parsed.error).toContain("client_secret");
|
|
986
|
-
expect(parsed.error).toContain("apps upsert");
|
|
987
|
-
});
|
|
988
|
-
});
|
|
989
|
-
|
|
990
553
|
// ---------------------------------------------------------------------------
|
|
991
554
|
// apps upsert --client-secret-credential-path
|
|
992
555
|
// ---------------------------------------------------------------------------
|
|
@@ -994,11 +557,6 @@ describe("assistant oauth connections connect <provider-key>", () => {
|
|
|
994
557
|
describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
995
558
|
beforeEach(() => {
|
|
996
559
|
mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
|
|
997
|
-
secureKeyStore = new Map();
|
|
998
|
-
metadataStore = [];
|
|
999
|
-
disconnectOAuthProviderCalls = [];
|
|
1000
|
-
disconnectOAuthProviderResult = "not-found";
|
|
1001
|
-
idCounter = 0;
|
|
1002
560
|
mockUpsertAppCalls = [];
|
|
1003
561
|
mockUpsertAppResult = {
|
|
1004
562
|
id: "app-upsert-1",
|
|
@@ -1207,14 +765,13 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
|
|
|
1207
765
|
// ping
|
|
1208
766
|
// ---------------------------------------------------------------------------
|
|
1209
767
|
|
|
1210
|
-
describe("assistant oauth
|
|
768
|
+
describe("assistant oauth ping <provider-key>", () => {
|
|
1211
769
|
beforeEach(() => {
|
|
1212
770
|
mockWithValidToken = async (_service, cb) => cb("mock-access-token-xyz");
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
idCounter = 0;
|
|
771
|
+
// Reset resolveOAuthConnection to default (unconfigured)
|
|
772
|
+
mockResolveOAuthConnection = async () => {
|
|
773
|
+
throw new Error("resolveOAuthConnection not configured in test");
|
|
774
|
+
};
|
|
1218
775
|
});
|
|
1219
776
|
|
|
1220
777
|
test("returns ok when ping endpoint returns 200", async () => {
|
|
@@ -1229,29 +786,27 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1229
786
|
createdAt: Date.now(),
|
|
1230
787
|
updatedAt: Date.now(),
|
|
1231
788
|
});
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
}
|
|
789
|
+
mockResolveOAuthConnection = async () => ({
|
|
790
|
+
id: "conn-1",
|
|
791
|
+
providerKey: "integration:google",
|
|
792
|
+
accountInfo: null,
|
|
793
|
+
request: async () => ({ status: 200, headers: {}, body: {} }),
|
|
794
|
+
withToken: async (fn) => fn("mock-access-token-xyz"),
|
|
795
|
+
});
|
|
796
|
+
const { exitCode, stdout } = await runCli([
|
|
797
|
+
"ping",
|
|
798
|
+
"integration:google",
|
|
799
|
+
"--json",
|
|
800
|
+
]);
|
|
801
|
+
expect(exitCode).toBe(0);
|
|
802
|
+
const parsed = JSON.parse(stdout);
|
|
803
|
+
expect(parsed.ok).toBe(true);
|
|
804
|
+
expect(parsed.status).toBe(200);
|
|
1249
805
|
});
|
|
1250
806
|
|
|
1251
807
|
test("exits 1 when provider not found", async () => {
|
|
1252
808
|
mockGetProvider = () => undefined;
|
|
1253
809
|
const { exitCode, stdout } = await runCli([
|
|
1254
|
-
"connections",
|
|
1255
810
|
"ping",
|
|
1256
811
|
"integration:unknown",
|
|
1257
812
|
"--json",
|
|
@@ -1259,7 +814,7 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1259
814
|
expect(exitCode).toBe(1);
|
|
1260
815
|
const parsed = JSON.parse(stdout);
|
|
1261
816
|
expect(parsed.ok).toBe(false);
|
|
1262
|
-
expect(parsed.error).toContain("
|
|
817
|
+
expect(parsed.error).toContain("Unknown provider");
|
|
1263
818
|
});
|
|
1264
819
|
|
|
1265
820
|
test("exits 1 when no ping URL configured", async () => {
|
|
@@ -1274,12 +829,7 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1274
829
|
createdAt: Date.now(),
|
|
1275
830
|
updatedAt: Date.now(),
|
|
1276
831
|
});
|
|
1277
|
-
const { exitCode, stdout } = await runCli([
|
|
1278
|
-
"connections",
|
|
1279
|
-
"ping",
|
|
1280
|
-
"telegram",
|
|
1281
|
-
"--json",
|
|
1282
|
-
]);
|
|
832
|
+
const { exitCode, stdout } = await runCli(["ping", "telegram", "--json"]);
|
|
1283
833
|
expect(exitCode).toBe(1);
|
|
1284
834
|
const parsed = JSON.parse(stdout);
|
|
1285
835
|
expect(parsed.ok).toBe(false);
|
|
@@ -1298,30 +848,25 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1298
848
|
createdAt: Date.now(),
|
|
1299
849
|
updatedAt: Date.now(),
|
|
1300
850
|
});
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
globalThis.fetch = originalFetch;
|
|
1318
|
-
}
|
|
851
|
+
mockResolveOAuthConnection = async () => ({
|
|
852
|
+
id: "conn-1",
|
|
853
|
+
providerKey: "integration:google",
|
|
854
|
+
accountInfo: null,
|
|
855
|
+
request: async () => ({ status: 403, headers: {}, body: "Forbidden" }),
|
|
856
|
+
withToken: async (fn) => fn("mock-access-token-xyz"),
|
|
857
|
+
});
|
|
858
|
+
const { exitCode, stdout } = await runCli([
|
|
859
|
+
"ping",
|
|
860
|
+
"integration:google",
|
|
861
|
+
"--json",
|
|
862
|
+
]);
|
|
863
|
+
expect(exitCode).toBe(1);
|
|
864
|
+
const parsed = JSON.parse(stdout);
|
|
865
|
+
expect(parsed.ok).toBe(false);
|
|
866
|
+
expect(parsed.status).toBe(403);
|
|
1319
867
|
});
|
|
1320
868
|
|
|
1321
|
-
test("exits 1 when no
|
|
1322
|
-
mockWithValidToken = async () => {
|
|
1323
|
-
throw new Error('No access token found for "integration:google".');
|
|
1324
|
-
};
|
|
869
|
+
test("exits 1 when no connection can be resolved", async () => {
|
|
1325
870
|
mockGetProvider = () => ({
|
|
1326
871
|
providerKey: "integration:google",
|
|
1327
872
|
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
@@ -1333,8 +878,10 @@ describe("assistant oauth connections ping <provider-key>", () => {
|
|
|
1333
878
|
createdAt: Date.now(),
|
|
1334
879
|
updatedAt: Date.now(),
|
|
1335
880
|
});
|
|
881
|
+
mockResolveOAuthConnection = async () => {
|
|
882
|
+
throw new Error('No access token found for "integration:google".');
|
|
883
|
+
};
|
|
1336
884
|
const { exitCode, stdout } = await runCli([
|
|
1337
|
-
"connections",
|
|
1338
885
|
"ping",
|
|
1339
886
|
"integration:google",
|
|
1340
887
|
"--json",
|