@vellumai/assistant 0.11.1 → 0.11.2-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -1,73 +1,25 @@
|
|
|
1
|
-
import { beforeEach, describe, expect,
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
runPlatform,
|
|
5
|
+
runPlatformCaught,
|
|
6
|
+
setupPlatformIpcMock,
|
|
7
|
+
} from "./helpers.js";
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
// Mock state
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
let mockCalls: Array<[string, Record<string, unknown>]> = [];
|
|
10
|
-
let mockResponse: unknown = { ok: true, result: { routes: [] } };
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Mocks
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
mock.module("../../../../ipc/cli-client.js", () => ({
|
|
17
|
-
cliIpcCall: async (method: string, params: Record<string, unknown>) => {
|
|
18
|
-
mockCalls.push([method, params]);
|
|
19
|
-
return mockResponse;
|
|
20
|
-
},
|
|
21
|
-
exitFromIpcResult: (_r: unknown, _cmd: unknown) => {
|
|
22
|
-
throw new Error("exitFromIpcResult called");
|
|
23
|
-
},
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
const { registerPlatformCommand } = await import("../index.js");
|
|
27
|
-
|
|
28
|
-
function buildProgram(): Command {
|
|
29
|
-
const program = new Command();
|
|
30
|
-
program.exitOverride();
|
|
31
|
-
registerPlatformCommand(program);
|
|
32
|
-
return program;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Tests
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
9
|
+
const ipc = setupPlatformIpcMock();
|
|
38
10
|
|
|
39
11
|
describe("assistant platform callback-routes list", () => {
|
|
40
12
|
beforeEach(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
process.exitCode = 0;
|
|
13
|
+
ipc.calls = [];
|
|
14
|
+
ipc.response = { ok: true, result: { routes: [] } };
|
|
44
15
|
});
|
|
45
16
|
|
|
46
17
|
test("returns empty list when no routes registered", async () => {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return true;
|
|
53
|
-
}) as typeof process.stdout.write;
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
await program.parseAsync([
|
|
57
|
-
"node",
|
|
58
|
-
"assistant",
|
|
59
|
-
"platform",
|
|
60
|
-
"callback-routes",
|
|
61
|
-
"list",
|
|
62
|
-
"--json",
|
|
63
|
-
]);
|
|
64
|
-
} finally {
|
|
65
|
-
process.stdout.write = origWrite;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
expect(mockCalls[0][0]).toBe("platform_callback_routes_list");
|
|
69
|
-
|
|
70
|
-
const parsed = JSON.parse(stdoutChunks.join(""));
|
|
18
|
+
const out = await runPlatform(["callback-routes", "list", "--json"]);
|
|
19
|
+
|
|
20
|
+
expect(ipc.calls[0][0]).toBe("platform_callback_routes_list");
|
|
21
|
+
|
|
22
|
+
const parsed = JSON.parse(out.join(""));
|
|
71
23
|
expect(parsed.ok).toBe(true);
|
|
72
24
|
expect(parsed.routes).toEqual([]);
|
|
73
25
|
});
|
|
@@ -91,30 +43,11 @@ describe("assistant platform callback-routes list", () => {
|
|
|
91
43
|
"https://dev-platform.vellum.ai/v1/gateway/callbacks/019d6d4f-6dbd-779f-91d3-cb273b9429a5/webhooks/telegram/",
|
|
92
44
|
},
|
|
93
45
|
];
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
process.stdout.write = ((chunk: unknown) => {
|
|
100
|
-
stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
101
|
-
return true;
|
|
102
|
-
}) as typeof process.stdout.write;
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
await program.parseAsync([
|
|
106
|
-
"node",
|
|
107
|
-
"assistant",
|
|
108
|
-
"platform",
|
|
109
|
-
"callback-routes",
|
|
110
|
-
"list",
|
|
111
|
-
"--json",
|
|
112
|
-
]);
|
|
113
|
-
} finally {
|
|
114
|
-
process.stdout.write = origWrite;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const parsed = JSON.parse(stdoutChunks.join(""));
|
|
46
|
+
ipc.response = { ok: true, result: { routes } };
|
|
47
|
+
|
|
48
|
+
const out = await runPlatform(["callback-routes", "list", "--json"]);
|
|
49
|
+
|
|
50
|
+
const parsed = JSON.parse(out.join(""));
|
|
118
51
|
expect(parsed.ok).toBe(true);
|
|
119
52
|
expect(parsed.routes).toHaveLength(2);
|
|
120
53
|
expect(parsed.routes[0].type).toBe("email");
|
|
@@ -122,27 +55,23 @@ describe("assistant platform callback-routes list", () => {
|
|
|
122
55
|
});
|
|
123
56
|
|
|
124
57
|
test("fails when platform credentials are missing", async () => {
|
|
125
|
-
|
|
58
|
+
ipc.response = {
|
|
126
59
|
ok: false,
|
|
127
60
|
error: "Platform credentials not available",
|
|
128
61
|
statusCode: 422,
|
|
129
62
|
};
|
|
130
63
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"list",
|
|
139
|
-
"--json",
|
|
140
|
-
]),
|
|
141
|
-
).rejects.toThrow("exitFromIpcResult called");
|
|
64
|
+
const { thrown } = await runPlatformCaught([
|
|
65
|
+
"callback-routes",
|
|
66
|
+
"list",
|
|
67
|
+
"--json",
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect((thrown as Error).message).toBe("exitFromIpcResult called");
|
|
142
71
|
});
|
|
143
72
|
|
|
144
73
|
test("callback-routes register calls platform_callback_routes_register", async () => {
|
|
145
|
-
|
|
74
|
+
ipc.response = {
|
|
146
75
|
ok: true,
|
|
147
76
|
result: {
|
|
148
77
|
callbackUrl:
|
|
@@ -152,40 +81,25 @@ describe("assistant platform callback-routes list", () => {
|
|
|
152
81
|
},
|
|
153
82
|
};
|
|
154
83
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
"platform",
|
|
168
|
-
"callback-routes",
|
|
169
|
-
"register",
|
|
170
|
-
"--path",
|
|
171
|
-
"webhooks/telegram",
|
|
172
|
-
"--type",
|
|
173
|
-
"telegram",
|
|
174
|
-
"--json",
|
|
175
|
-
]);
|
|
176
|
-
} finally {
|
|
177
|
-
process.stdout.write = origWrite;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
expect(mockCalls[0][0]).toBe("platform_callback_routes_register");
|
|
181
|
-
expect((mockCalls[0][1].body as Record<string, unknown>).path).toBe(
|
|
84
|
+
const out = await runPlatform([
|
|
85
|
+
"callback-routes",
|
|
86
|
+
"register",
|
|
87
|
+
"--path",
|
|
88
|
+
"webhooks/telegram",
|
|
89
|
+
"--type",
|
|
90
|
+
"telegram",
|
|
91
|
+
"--json",
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
expect(ipc.calls[0][0]).toBe("platform_callback_routes_register");
|
|
95
|
+
expect((ipc.calls[0][1].body as Record<string, unknown>).path).toBe(
|
|
182
96
|
"webhooks/telegram",
|
|
183
97
|
);
|
|
184
|
-
expect((
|
|
98
|
+
expect((ipc.calls[0][1].body as Record<string, unknown>).type).toBe(
|
|
185
99
|
"telegram",
|
|
186
100
|
);
|
|
187
101
|
|
|
188
|
-
const parsed = JSON.parse(
|
|
102
|
+
const parsed = JSON.parse(out.join(""));
|
|
189
103
|
expect(parsed.ok).toBe(true);
|
|
190
104
|
expect(parsed.callbackPath).toBe("webhooks/telegram");
|
|
191
105
|
});
|
|
@@ -1,69 +1,13 @@
|
|
|
1
|
-
import { beforeEach, describe, expect,
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { runPlatform, setupPlatformIpcMock } from "./helpers.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
// Mock state
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
let mockCalls: Array<[string, Record<string, unknown>]> = [];
|
|
10
|
-
let mockResponse: unknown = {
|
|
11
|
-
ok: true,
|
|
12
|
-
result: {
|
|
13
|
-
remaining: 42.17,
|
|
14
|
-
settled: 50,
|
|
15
|
-
pending: 7.83,
|
|
16
|
-
unit: "USD",
|
|
17
|
-
stale: false,
|
|
18
|
-
as_of: "2026-07-06T00:00:00.000Z",
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Mocks
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
mock.module("../../../../ipc/cli-client.js", () => ({
|
|
27
|
-
cliIpcCall: async (method: string, params: Record<string, unknown>) => {
|
|
28
|
-
mockCalls.push([method, params]);
|
|
29
|
-
return mockResponse;
|
|
30
|
-
},
|
|
31
|
-
exitFromIpcResult: (_r: unknown, _cmd: unknown) => {
|
|
32
|
-
throw new Error("exitFromIpcResult called");
|
|
33
|
-
},
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
const { registerPlatformCommand } = await import("../index.js");
|
|
37
|
-
|
|
38
|
-
function buildProgram(): Command {
|
|
39
|
-
const program = new Command();
|
|
40
|
-
program.exitOverride();
|
|
41
|
-
registerPlatformCommand(program);
|
|
42
|
-
return program;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function captureStdout(fn: () => Promise<void>): Promise<string[]> {
|
|
46
|
-
const chunks: string[] = [];
|
|
47
|
-
const origWrite = process.stdout.write.bind(process.stdout);
|
|
48
|
-
process.stdout.write = ((chunk: unknown) => {
|
|
49
|
-
chunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
50
|
-
return true;
|
|
51
|
-
}) as typeof process.stdout.write;
|
|
52
|
-
return fn()
|
|
53
|
-
.then(() => chunks)
|
|
54
|
-
.finally(() => {
|
|
55
|
-
process.stdout.write = origWrite;
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Tests
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
5
|
+
const ipc = setupPlatformIpcMock();
|
|
62
6
|
|
|
63
7
|
describe("assistant platform credits", () => {
|
|
64
8
|
beforeEach(() => {
|
|
65
|
-
|
|
66
|
-
|
|
9
|
+
ipc.calls = [];
|
|
10
|
+
ipc.response = {
|
|
67
11
|
ok: true,
|
|
68
12
|
result: {
|
|
69
13
|
remaining: 42.17,
|
|
@@ -74,22 +18,12 @@ describe("assistant platform credits", () => {
|
|
|
74
18
|
as_of: "2026-07-06T00:00:00.000Z",
|
|
75
19
|
},
|
|
76
20
|
};
|
|
77
|
-
process.exitCode = 0;
|
|
78
21
|
});
|
|
79
22
|
|
|
80
23
|
test("calls platform_credits and emits balance JSON with --json", async () => {
|
|
81
|
-
const out = await
|
|
82
|
-
const program = buildProgram();
|
|
83
|
-
await program.parseAsync([
|
|
84
|
-
"node",
|
|
85
|
-
"assistant",
|
|
86
|
-
"platform",
|
|
87
|
-
"credits",
|
|
88
|
-
"--json",
|
|
89
|
-
]);
|
|
90
|
-
});
|
|
24
|
+
const out = await runPlatform(["credits", "--json"]);
|
|
91
25
|
|
|
92
|
-
expect(
|
|
26
|
+
expect(ipc.calls[0][0]).toBe("platform_credits");
|
|
93
27
|
|
|
94
28
|
const parsed = JSON.parse(out.join(""));
|
|
95
29
|
expect(parsed.remaining).toBe(42.17);
|
|
@@ -100,12 +34,9 @@ describe("assistant platform credits", () => {
|
|
|
100
34
|
});
|
|
101
35
|
|
|
102
36
|
test("plain text mode does not emit JSON to stdout", async () => {
|
|
103
|
-
const out = await
|
|
104
|
-
const program = buildProgram();
|
|
105
|
-
await program.parseAsync(["node", "assistant", "platform", "credits"]);
|
|
106
|
-
});
|
|
37
|
+
const out = await runPlatform(["credits"]);
|
|
107
38
|
|
|
108
|
-
// Plain-text mode logs via log.info
|
|
39
|
+
// Plain-text mode logs via log.info; verify writeOutput (JSON) was NOT called
|
|
109
40
|
expect(() => JSON.parse(out.join("").trim())).toThrow();
|
|
110
41
|
});
|
|
111
42
|
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { mock } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
|
|
5
|
+
/** Mutable per-file mock state returned by {@link setupPlatformIpcMock}. */
|
|
6
|
+
export interface PlatformIpcMockState {
|
|
7
|
+
/** Every (method, params) pair passed to the mocked cliIpcCall. */
|
|
8
|
+
calls: Array<[string, Record<string, unknown>]>;
|
|
9
|
+
/** The value the mocked cliIpcCall resolves with. */
|
|
10
|
+
response: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Install the shared IPC client mock for a platform command test file.
|
|
15
|
+
*
|
|
16
|
+
* bun's mock.module is global per test file, so each file must call this
|
|
17
|
+
* once at module top level (before importing ../index.js) and drive the
|
|
18
|
+
* mock through the returned state object. The mocked exitFromIpcResult
|
|
19
|
+
* throws so tests can assert the error path without exiting the process.
|
|
20
|
+
*/
|
|
21
|
+
export function setupPlatformIpcMock(): PlatformIpcMockState {
|
|
22
|
+
const state: PlatformIpcMockState = { calls: [], response: undefined };
|
|
23
|
+
mock.module("../../../../ipc/cli-client.js", () => ({
|
|
24
|
+
cliIpcCall: async (method: string, params: Record<string, unknown>) => {
|
|
25
|
+
state.calls.push([method, params]);
|
|
26
|
+
return state.response;
|
|
27
|
+
},
|
|
28
|
+
exitFromIpcResult: (_r: unknown, _cmd: unknown) => {
|
|
29
|
+
throw new Error("exitFromIpcResult called");
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
return state;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build a fresh commander program with the platform command registered.
|
|
37
|
+
* Imported lazily so the file-level mock installed by
|
|
38
|
+
* {@link setupPlatformIpcMock} is in place first.
|
|
39
|
+
*/
|
|
40
|
+
export async function buildPlatformProgram(): Promise<Command> {
|
|
41
|
+
const { registerPlatformCommand } = await import("../index.js");
|
|
42
|
+
const program = new Command();
|
|
43
|
+
program.exitOverride();
|
|
44
|
+
registerPlatformCommand(program);
|
|
45
|
+
return program;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Capture everything written to stdout while fn runs. */
|
|
49
|
+
export function captureStdout(fn: () => Promise<void>): Promise<string[]> {
|
|
50
|
+
const chunks: string[] = [];
|
|
51
|
+
const origWrite = process.stdout.write.bind(process.stdout);
|
|
52
|
+
process.stdout.write = ((chunk: unknown) => {
|
|
53
|
+
chunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
54
|
+
return true;
|
|
55
|
+
}) as typeof process.stdout.write;
|
|
56
|
+
return fn()
|
|
57
|
+
.then(() => chunks)
|
|
58
|
+
.finally(() => {
|
|
59
|
+
process.stdout.write = origWrite;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Run 'assistant platform <args>' against a fresh program, capturing stdout. */
|
|
64
|
+
export async function runPlatform(args: string[]): Promise<string[]> {
|
|
65
|
+
const { out, thrown } = await runPlatformCaught(args);
|
|
66
|
+
if (thrown !== undefined) {
|
|
67
|
+
throw thrown;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Like {@link runPlatform}, but capture whatever the parse throws instead of
|
|
74
|
+
* propagating it (the mocked exitFromIpcResult error, Commander's exitOverride
|
|
75
|
+
* on --help), so tests can assert on both the output and the thrown value.
|
|
76
|
+
*/
|
|
77
|
+
export async function runPlatformCaught(
|
|
78
|
+
args: string[],
|
|
79
|
+
): Promise<{ out: string[]; thrown: unknown }> {
|
|
80
|
+
let thrown: unknown;
|
|
81
|
+
const out = await captureStdout(async () => {
|
|
82
|
+
const program = await buildPlatformProgram();
|
|
83
|
+
try {
|
|
84
|
+
await program.parseAsync(["node", "assistant", "platform", ...args]);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
thrown = err;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return { out, thrown };
|
|
90
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
runPlatform,
|
|
5
|
+
runPlatformCaught,
|
|
6
|
+
setupPlatformIpcMock,
|
|
7
|
+
} from "./helpers.js";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Fixtures
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
const invoiceA = {
|
|
14
|
+
id: "in_a",
|
|
15
|
+
number: "INV-0001",
|
|
16
|
+
status: "paid",
|
|
17
|
+
currency: "usd",
|
|
18
|
+
amount_due: 1234,
|
|
19
|
+
amount_paid: 1234,
|
|
20
|
+
amount_remaining: 0,
|
|
21
|
+
created: 1750000000,
|
|
22
|
+
hosted_invoice_url: "https://pay.example.com/in_a",
|
|
23
|
+
invoice_pdf: "https://pay.example.com/in_a.pdf",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const invoiceB = {
|
|
27
|
+
id: "in_b",
|
|
28
|
+
number: null,
|
|
29
|
+
status: "open",
|
|
30
|
+
currency: "usd",
|
|
31
|
+
amount_due: 5600,
|
|
32
|
+
amount_paid: 0,
|
|
33
|
+
amount_remaining: 5600,
|
|
34
|
+
created: 1747000000,
|
|
35
|
+
hosted_invoice_url: null,
|
|
36
|
+
invoice_pdf: null,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ipc = setupPlatformIpcMock();
|
|
40
|
+
|
|
41
|
+
function runInvoices(args: string[]): Promise<string[]> {
|
|
42
|
+
return runPlatform(["invoices", ...args]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Tests
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
describe("assistant platform invoices list", () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
ipc.calls = [];
|
|
52
|
+
ipc.response = {
|
|
53
|
+
ok: true,
|
|
54
|
+
result: { invoices: [invoiceA, invoiceB], has_more: false },
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("calls platform_invoices_list and emits the page as JSON with --json", async () => {
|
|
59
|
+
const out = await runInvoices(["list", "--json"]);
|
|
60
|
+
|
|
61
|
+
expect(ipc.calls[0][0]).toBe("platform_invoices_list");
|
|
62
|
+
expect(ipc.calls[0][1]).toEqual({});
|
|
63
|
+
|
|
64
|
+
const parsed = JSON.parse(out.join(""));
|
|
65
|
+
expect(parsed.invoices).toHaveLength(2);
|
|
66
|
+
expect(parsed.invoices[0].id).toBe("in_a");
|
|
67
|
+
expect(parsed.has_more).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("forwards --starting-after as the starting_after query param", async () => {
|
|
71
|
+
await runInvoices(["list", "--starting-after", "in_a", "--json"]);
|
|
72
|
+
|
|
73
|
+
expect(ipc.calls[0][0]).toBe("platform_invoices_list");
|
|
74
|
+
expect(ipc.calls[0][1]).toEqual({
|
|
75
|
+
queryParams: { starting_after: "in_a" },
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("plain text mode renders number, status, and amount lines", async () => {
|
|
80
|
+
const out = await runInvoices(["list"]);
|
|
81
|
+
const text = out.join("");
|
|
82
|
+
|
|
83
|
+
expect(text).toContain("INV-0001");
|
|
84
|
+
expect(text).toContain("Status: paid");
|
|
85
|
+
expect(text).toContain("Amount: USD 12.34");
|
|
86
|
+
expect(text).toContain("https://pay.example.com/in_a");
|
|
87
|
+
// Invoice with a null number falls back to its id.
|
|
88
|
+
expect(text).toContain("in_b");
|
|
89
|
+
expect(text).toContain("Amount: USD 56.00");
|
|
90
|
+
// Not JSON output.
|
|
91
|
+
expect(() => JSON.parse(text.trim())).toThrow();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("plain text mode reports an empty page", async () => {
|
|
95
|
+
ipc.response = { ok: true, result: { invoices: [], has_more: false } };
|
|
96
|
+
|
|
97
|
+
const out = await runInvoices(["list"]);
|
|
98
|
+
|
|
99
|
+
expect(out.join("").trim()).toBe("No invoices found.");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("plain text mode ends with a paging hint when has_more is true", async () => {
|
|
103
|
+
ipc.response = {
|
|
104
|
+
ok: true,
|
|
105
|
+
result: { invoices: [invoiceA, invoiceB], has_more: true },
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const out = await runInvoices(["list"]);
|
|
109
|
+
|
|
110
|
+
expect(out.join("").trim()).toEndWith(
|
|
111
|
+
"More invoices available. Run 'assistant platform invoices list --starting-after in_b' for the next page.",
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("plain text mode omits the paging hint when has_more is false", async () => {
|
|
116
|
+
const out = await runInvoices(["list"]);
|
|
117
|
+
|
|
118
|
+
expect(out.join("")).not.toContain("More invoices available");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("assistant platform invoices get", () => {
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
ipc.calls = [];
|
|
125
|
+
ipc.response = { ok: true, result: invoiceA };
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("calls platform_invoices_by_id_get with the id path param and emits JSON with --json", async () => {
|
|
129
|
+
const out = await runInvoices(["get", "in_123", "--json"]);
|
|
130
|
+
|
|
131
|
+
expect(ipc.calls[0][0]).toBe("platform_invoices_by_id_get");
|
|
132
|
+
expect(ipc.calls[0][1]).toEqual({ pathParams: { id: "in_123" } });
|
|
133
|
+
|
|
134
|
+
const parsed = JSON.parse(out.join(""));
|
|
135
|
+
expect(parsed.id).toBe("in_a");
|
|
136
|
+
expect(parsed.amount_due).toBe(1234);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("plain text mode renders the invoice block including the PDF link", async () => {
|
|
140
|
+
const out = await runInvoices(["get", "in_a"]);
|
|
141
|
+
const text = out.join("");
|
|
142
|
+
|
|
143
|
+
expect(text).toContain("INV-0001");
|
|
144
|
+
expect(text).toContain("Status: paid");
|
|
145
|
+
expect(text).toContain("Amount: USD 12.34");
|
|
146
|
+
expect(text).toContain("PDF: https://pay.example.com/in_a.pdf");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("plain text mode scales zero-decimal currencies without dividing by 100", async () => {
|
|
150
|
+
ipc.response = {
|
|
151
|
+
ok: true,
|
|
152
|
+
result: { ...invoiceA, currency: "jpy", amount_due: 1200 },
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const out = await runInvoices(["get", "in_a"]);
|
|
156
|
+
|
|
157
|
+
expect(out.join("")).toContain("Amount: JPY 1,200");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("plain text mode uses three decimals for three-decimal currencies", async () => {
|
|
161
|
+
ipc.response = {
|
|
162
|
+
ok: true,
|
|
163
|
+
result: { ...invoiceA, currency: "bhd", amount_due: 12345 },
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const out = await runInvoices(["get", "in_a"]);
|
|
167
|
+
|
|
168
|
+
expect(out.join("")).toContain("Amount: BHD 12.345");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("plain text mode keeps Stripe's two-decimal scaling for ISK despite ISO zero-decimal metadata", async () => {
|
|
172
|
+
ipc.response = {
|
|
173
|
+
ok: true,
|
|
174
|
+
result: { ...invoiceA, currency: "isk", amount_due: 500 },
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const out = await runInvoices(["get", "in_a"]);
|
|
178
|
+
|
|
179
|
+
expect(out.join("")).toContain("Amount: ISK 5.00");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("plain text mode falls back to raw minor units for invalid currency codes", async () => {
|
|
183
|
+
ipc.response = {
|
|
184
|
+
ok: true,
|
|
185
|
+
result: { ...invoiceA, currency: "zz", amount_due: 1234 },
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const out = await runInvoices(["get", "in_a"]);
|
|
189
|
+
|
|
190
|
+
expect(out.join("")).toContain("Amount: 1234 ZZ (minor units)");
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("assistant platform invoices error handling", () => {
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
ipc.calls = [];
|
|
197
|
+
ipc.response = {
|
|
198
|
+
ok: false,
|
|
199
|
+
error: "Platform credentials not available",
|
|
200
|
+
statusCode: 422,
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("list exits via exitFromIpcResult on IPC failure without writing output", async () => {
|
|
205
|
+
const { out, thrown } = await runPlatformCaught(["invoices", "list"]);
|
|
206
|
+
|
|
207
|
+
expect((thrown as Error).message).toBe("exitFromIpcResult called");
|
|
208
|
+
expect(out.join("")).toBe("");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("get exits via exitFromIpcResult on IPC failure without writing output", async () => {
|
|
212
|
+
const { out, thrown } = await runPlatformCaught([
|
|
213
|
+
"invoices",
|
|
214
|
+
"get",
|
|
215
|
+
"in_missing",
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
expect((thrown as Error).message).toBe("exitFromIpcResult called");
|
|
219
|
+
expect(out.join("")).toBe("");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("assistant platform invoices --help", () => {
|
|
224
|
+
test("group help renders both subcommands", async () => {
|
|
225
|
+
const { out } = await runPlatformCaught(["invoices", "--help"]);
|
|
226
|
+
const text = out.join("");
|
|
227
|
+
|
|
228
|
+
expect(text).toContain("list");
|
|
229
|
+
expect(text).toContain("get");
|
|
230
|
+
expect(text).toContain("Stripe invoice history");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("list help renders the --starting-after option", async () => {
|
|
234
|
+
const { out } = await runPlatformCaught(["invoices", "list", "--help"]);
|
|
235
|
+
|
|
236
|
+
expect(out.join("")).toContain("--starting-after <invoice-id>");
|
|
237
|
+
});
|
|
238
|
+
});
|