@indigoai-us/hq-cli 5.77.14 → 5.78.0
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/CHANGELOG.md +23 -0
- package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
- package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
- package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
- package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
- package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
- package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
- package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
- package/assets/scaffold/core/scripts/worktree.sh +136 -0
- package/dist/commands/agents.js +28 -12
- package/dist/commands/api-keys.js +100 -20
- package/dist/commands/core.d.ts +90 -0
- package/dist/commands/core.js +279 -0
- package/dist/commands/files-browse.d.ts +21 -0
- package/dist/commands/files-browse.js +145 -24
- package/dist/commands/files.js +33 -7
- package/dist/commands/members.js +16 -5
- package/dist/commands/pack-install.d.ts +14 -0
- package/dist/commands/pack-install.js +42 -2
- package/dist/commands/pkg-install.js +39 -2
- package/dist/commands/run.js +6 -2
- package/dist/commands/secrets.js +120 -65
- package/dist/main.js +6 -0
- package/dist/utils/api-key-command-gate.d.ts +45 -0
- package/dist/utils/api-key-command-gate.js +51 -0
- package/dist/utils/hq-roots.d.ts +91 -0
- package/dist/utils/hq-roots.js +212 -0
- package/dist/utils/resolve-vault-credential.d.ts +63 -3
- package/dist/utils/resolve-vault-credential.js +152 -6
- package/dist/utils/run-bundled-script.d.ts +66 -0
- package/dist/utils/run-bundled-script.js +80 -0
- package/dist/utils/vault-api.d.ts +21 -0
- package/dist/utils/vault-api.js +74 -3
- package/package.json +7 -1
- package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
- package/e2e/cli.test.ts +0 -128
- package/eslint.config.js +0 -37
- package/pnpm-workspace.yaml +0 -4
- package/scripts/chmod-bins.mjs +0 -49
- package/scripts/generate-dsn.mjs +0 -26
- package/scripts/generate-scan-packages-table.mjs +0 -113
- package/scripts/smoke-sources-signals.sh +0 -103
- package/src/bin/hq-auth-refresh.ts +0 -43
- package/src/cli-version.ts +0 -13
- package/src/commands/__fixtures__/make-tar.ts +0 -126
- package/src/commands/_patterns.ts +0 -10
- package/src/commands/add.ts +0 -75
- package/src/commands/agents.test.ts +0 -527
- package/src/commands/agents.ts +0 -923
- package/src/commands/api-keys.test.ts +0 -291
- package/src/commands/api-keys.ts +0 -382
- package/src/commands/artifact-verify.test.ts +0 -177
- package/src/commands/auth.test.ts +0 -82
- package/src/commands/auth.ts +0 -213
- package/src/commands/billing.test.ts +0 -158
- package/src/commands/billing.ts +0 -146
- package/src/commands/channels.test.ts +0 -74
- package/src/commands/channels.ts +0 -91
- package/src/commands/cloud-demote.test.ts +0 -401
- package/src/commands/cloud-demote.ts +0 -278
- package/src/commands/cloud-provision.test.ts +0 -1117
- package/src/commands/cloud-provision.ts +0 -942
- package/src/commands/cloud.pull-all.test.ts +0 -711
- package/src/commands/cloud.pull-per-company.test.ts +0 -188
- package/src/commands/cloud.push-all.test.ts +0 -485
- package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
- package/src/commands/cloud.selectors.test.ts +0 -208
- package/src/commands/cloud.status.test.ts +0 -160
- package/src/commands/cloud.test.ts +0 -41
- package/src/commands/cloud.ts +0 -2130
- package/src/commands/company.test.ts +0 -202
- package/src/commands/company.ts +0 -148
- package/src/commands/creators.test.ts +0 -60
- package/src/commands/creators.ts +0 -117
- package/src/commands/crm.test.ts +0 -179
- package/src/commands/crm.ts +0 -236
- package/src/commands/db-migrate.ts +0 -55
- package/src/commands/db-provision.ts +0 -114
- package/src/commands/db-sql.ts +0 -124
- package/src/commands/db-status.ts +0 -108
- package/src/commands/db.ts +0 -26
- package/src/commands/dm.test.ts +0 -748
- package/src/commands/dm.ts +0 -1226
- package/src/commands/feedback.test.ts +0 -413
- package/src/commands/feedback.ts +0 -169
- package/src/commands/files-browse.test.ts +0 -1249
- package/src/commands/files-browse.ts +0 -1506
- package/src/commands/files-delete.test.ts +0 -484
- package/src/commands/files-recovery.test.ts +0 -370
- package/src/commands/files.test.ts +0 -722
- package/src/commands/files.ts +0 -1446
- package/src/commands/group-grants.test.ts +0 -330
- package/src/commands/group-grants.ts +0 -455
- package/src/commands/groups.test.ts +0 -44
- package/src/commands/groups.ts +0 -368
- package/src/commands/integrations.test.ts +0 -633
- package/src/commands/integrations.ts +0 -507
- package/src/commands/list.ts +0 -85
- package/src/commands/login.ts +0 -52
- package/src/commands/logout.ts +0 -30
- package/src/commands/marketplace-install.test.ts +0 -543
- package/src/commands/marketplace-security.test.ts +0 -646
- package/src/commands/mcp-registration.test.ts +0 -2787
- package/src/commands/mcp-registration.ts +0 -2612
- package/src/commands/mcp-status.test.ts +0 -483
- package/src/commands/mcp-status.ts +0 -575
- package/src/commands/mcp-status.us011.test.ts +0 -243
- package/src/commands/meetings.test.ts +0 -522
- package/src/commands/meetings.ts +0 -789
- package/src/commands/members.test.ts +0 -1474
- package/src/commands/members.ts +0 -1181
- package/src/commands/onboard-identity-guard.test.ts +0 -99
- package/src/commands/onboard-identity-guard.ts +0 -103
- package/src/commands/onboard-join.test.ts +0 -96
- package/src/commands/onboard-join.ts +0 -101
- package/src/commands/onboard-warning.test.ts +0 -26
- package/src/commands/onboard-warning.ts +0 -12
- package/src/commands/onboard.ts +0 -267
- package/src/commands/outposts-heartbeat.test.ts +0 -299
- package/src/commands/outposts-heartbeat.ts +0 -310
- package/src/commands/outposts-replica-sync.test.ts +0 -272
- package/src/commands/outposts-self-deploy.test.ts +0 -243
- package/src/commands/outposts.test.ts +0 -1039
- package/src/commands/outposts.ts +0 -1935
- package/src/commands/pack-install-secret-authorization.test.ts +0 -115
- package/src/commands/pack-install.test.ts +0 -1469
- package/src/commands/pack-install.ts +0 -2256
- package/src/commands/pack-update-cache.test.ts +0 -149
- package/src/commands/packs-update-api-key.test.ts +0 -105
- package/src/commands/packs.test.ts +0 -88
- package/src/commands/packs.ts +0 -622
- package/src/commands/people.test.ts +0 -807
- package/src/commands/people.ts +0 -422
- package/src/commands/pkg-install.dispatch.test.ts +0 -103
- package/src/commands/pkg-install.ts +0 -221
- package/src/commands/pkg-list.test.ts +0 -79
- package/src/commands/pkg-list.ts +0 -13
- package/src/commands/pkg-remove.ts +0 -71
- package/src/commands/pkg-update.ts +0 -189
- package/src/commands/publish.test.ts +0 -650
- package/src/commands/publish.ts +0 -580
- package/src/commands/reindex.test.ts +0 -259
- package/src/commands/reindex.ts +0 -274
- package/src/commands/rescue.test.ts +0 -39
- package/src/commands/rescue.ts +0 -210
- package/src/commands/run.env-local.test.ts +0 -88
- package/src/commands/run.test.ts +0 -45
- package/src/commands/run.ts +0 -190
- package/src/commands/safe-extract.test.ts +0 -459
- package/src/commands/safe-extract.ts +0 -444
- package/src/commands/secrets-scope.test.ts +0 -56
- package/src/commands/secrets-scope.ts +0 -32
- package/src/commands/secrets.parse-destination.test.ts +0 -38
- package/src/commands/secrets.test.ts +0 -2050
- package/src/commands/secrets.ts +0 -2249
- package/src/commands/signals.ts +0 -351
- package/src/commands/skill.test.ts +0 -770
- package/src/commands/skill.ts +0 -796
- package/src/commands/sources.ts +0 -362
- package/src/commands/sync-mode.test.ts +0 -366
- package/src/commands/sync-mode.ts +0 -387
- package/src/commands/sync-narrow.test.ts +0 -633
- package/src/commands/sync-narrow.ts +0 -555
- package/src/commands/sync.ts +0 -165
- package/src/commands/team-sync.ts +0 -628
- package/src/commands/update.ts +0 -71
- package/src/commands/whoami.ts +0 -83
- package/src/commands/workers.test.ts +0 -158
- package/src/commands/workers.ts +0 -301
- package/src/index.test.ts +0 -32
- package/src/index.ts +0 -20
- package/src/lib/db/control-plane.test.ts +0 -59
- package/src/lib/db/control-plane.ts +0 -113
- package/src/lib/db/local.test.ts +0 -81
- package/src/lib/db/local.ts +0 -148
- package/src/lib/db/migrate.test.ts +0 -133
- package/src/lib/db/migrate.ts +0 -137
- package/src/lib/db/paths.test.ts +0 -112
- package/src/lib/db/paths.ts +0 -128
- package/src/lib/db/remote-engine.test.ts +0 -44
- package/src/lib/db/remote-engine.ts +0 -148
- package/src/lib/db/remote-sql.test.ts +0 -32
- package/src/lib/db/remote-sql.ts +0 -62
- package/src/lib/db/sql.test.ts +0 -106
- package/src/lib/db/sql.ts +0 -192
- package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
- package/src/lib/local-tree-diff.test.ts +0 -301
- package/src/lib/local-tree-diff.ts +0 -362
- package/src/lib/narrow-hint-banner.test.ts +0 -235
- package/src/lib/narrow-hint-banner.ts +0 -212
- package/src/lib/plan-limit-nag.test.ts +0 -317
- package/src/lib/plan-limit-nag.ts +0 -264
- package/src/main.ts +0 -371
- package/src/node-preflight.test.ts +0 -60
- package/src/node-preflight.ts +0 -67
- package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
- package/src/outpost/session-heartbeat-guard.test.ts +0 -105
- package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
- package/src/outpost/session-heartbeat-publisher.ts +0 -186
- package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
- package/src/outpost/session-heartbeat.test.ts +0 -459
- package/src/outpost/session-heartbeat.ts +0 -877
- package/src/packaging.test.ts +0 -109
- package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
- package/src/run/discover-schemas.test.ts +0 -153
- package/src/run/discover-schemas.ts +0 -79
- package/src/run/hq-plugin.test.ts +0 -371
- package/src/run/hq-plugin.ts +0 -277
- package/src/run/varlock-shape.test.ts +0 -57
- package/src/schemas/hq-package.schema.json +0 -155
- package/src/sentry-before-send.test.ts +0 -359
- package/src/sentry-before-send.ts +0 -221
- package/src/sentry-epipe.test.ts +0 -37
- package/src/sentry-release.test.ts +0 -54
- package/src/sentry.test.ts +0 -24
- package/src/sentry.ts +0 -54
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/types.ts +0 -146
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
- package/src/utils/auth-error.test.ts +0 -40
- package/src/utils/auth-error.ts +0 -42
- package/src/utils/billing-gate.test.ts +0 -95
- package/src/utils/billing-gate.ts +0 -228
- package/src/utils/breadcrumb-buffer.ts +0 -18
- package/src/utils/cli-telemetry.test.ts +0 -153
- package/src/utils/cli-telemetry.ts +0 -61
- package/src/utils/cognito-session.machine.test.ts +0 -109
- package/src/utils/cognito-session.refresh.test.ts +0 -84
- package/src/utils/cognito-session.test.ts +0 -328
- package/src/utils/cognito-session.ts +0 -448
- package/src/utils/company-selection-error.test.ts +0 -42
- package/src/utils/company-selection-error.ts +0 -45
- package/src/utils/contribution-table.ts +0 -83
- package/src/utils/environmental-error.test.ts +0 -45
- package/src/utils/environmental-error.ts +0 -39
- package/src/utils/epipe.test.ts +0 -28
- package/src/utils/epipe.ts +0 -29
- package/src/utils/expected-cli-error.test.ts +0 -28
- package/src/utils/expected-cli-error.ts +0 -39
- package/src/utils/feedback-diagnostics.test.ts +0 -183
- package/src/utils/feedback-diagnostics.ts +0 -123
- package/src/utils/feedback-screenshots.test.ts +0 -134
- package/src/utils/feedback-screenshots.ts +0 -124
- package/src/utils/feedback-versions.test.ts +0 -98
- package/src/utils/feedback-versions.ts +0 -68
- package/src/utils/git.ts +0 -74
- package/src/utils/integrity.ts +0 -59
- package/src/utils/intercepted-process-exit.test.ts +0 -37
- package/src/utils/intercepted-process-exit.ts +0 -36
- package/src/utils/login-provider.test.ts +0 -42
- package/src/utils/login-provider.ts +0 -30
- package/src/utils/manifest.test.ts +0 -167
- package/src/utils/manifest.ts +0 -131
- package/src/utils/pack-contributions.test.ts +0 -603
- package/src/utils/pack-contributions.ts +0 -450
- package/src/utils/people.ts +0 -215
- package/src/utils/registry-client.ts +0 -204
- package/src/utils/registry.ts +0 -90
- package/src/utils/resolve-vault-credential.test.ts +0 -69
- package/src/utils/resolve-vault-credential.ts +0 -60
- package/src/utils/sandbox-runner-client.test.ts +0 -279
- package/src/utils/sandbox-runner-client.ts +0 -260
- package/src/utils/secrets-cache.ts +0 -183
- package/src/utils/sentry-identity.ts +0 -45
- package/src/utils/settle-with-timeout.test.ts +0 -21
- package/src/utils/settle-with-timeout.ts +0 -22
- package/src/utils/user-yaml-error.test.ts +0 -24
- package/src/utils/user-yaml-error.ts +0 -30
- package/src/utils/vault-api.test.ts +0 -536
- package/src/utils/vault-api.ts +0 -382
- package/src/utils/version-check.test.ts +0 -176
- package/src/utils/version-check.ts +0 -155
- package/src/utils/version-gate.test.ts +0 -1024
- package/src/utils/version-gate.ts +0 -702
- package/test/commands/db-tenant-isolation.test.ts +0 -94
- package/test/commands/db.test.ts +0 -85
- package/test/commands/signals.test.ts +0 -207
- package/test/commands/sources.test.ts +0 -231
- package/test/e2e/smoke-install-mcp.sh +0 -113
- package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
- package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
- package/test/fixtures/signals/action_item/sample.md +0 -16
- package/test/fixtures/signals/summary/sample.md +0 -12
- package/test/fixtures/sources/meetings/sample.md +0 -25
- package/test/helpers/cli-runner.ts +0 -150
- package/test/helpers/s3-list-mock.ts +0 -79
- package/test/helpers/vault-service-mock.ts +0 -295
- package/test/sources-signals/smoke.test.ts +0 -228
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -11
- package/vitest.e2e.config.ts +0 -7
|
@@ -1,633 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for `hq integrations list|tools|call|approve` (integrations.ts).
|
|
3
|
-
*
|
|
4
|
-
* Coverage:
|
|
5
|
-
* - toolPrefixForProvider — factory-prefix stripping + slug normalization
|
|
6
|
-
* (must mirror hq-pro's factoryToolPrefix so composed HQ tool names hit
|
|
7
|
-
* registered gateway tools)
|
|
8
|
-
* - selectConnection — by id, by provider (factory:-prefixed match),
|
|
9
|
-
* single-active default, and the guiding errors for none/ambiguous
|
|
10
|
-
* - unwrapGatewayResult — MCP text-content unwrapping + non-JSON fallback
|
|
11
|
-
* - queued-outcome detection + the approve hint in `call`
|
|
12
|
-
* - approve — posts to the confirm route and prints the unwrapped result
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { Command } from "commander";
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest";
|
|
17
|
-
|
|
18
|
-
vi.mock("../utils/cognito-session.js", async (importOriginal) => {
|
|
19
|
-
const original =
|
|
20
|
-
await importOriginal<typeof import("../utils/cognito-session.js")>();
|
|
21
|
-
return {
|
|
22
|
-
...original,
|
|
23
|
-
ensureCognitoIdToken: vi.fn(async () => "test-id-token"),
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
vi.mock("../utils/vault-api.js", async (importOriginal) => {
|
|
28
|
-
const original = await importOriginal<typeof import("../utils/vault-api.js")>();
|
|
29
|
-
return {
|
|
30
|
-
...original,
|
|
31
|
-
getCompanyUid: vi.fn(async () => "cmp_acme"),
|
|
32
|
-
vaultApiFetch: vi.fn(),
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
37
|
-
import { AuthError, isAuthError } from "../utils/auth-error.js";
|
|
38
|
-
import {
|
|
39
|
-
IntegrationsCliError,
|
|
40
|
-
queuedOutcome,
|
|
41
|
-
registerIntegrationsCommand,
|
|
42
|
-
selectConnection,
|
|
43
|
-
toolPrefixForProvider,
|
|
44
|
-
unwrapGatewayResult,
|
|
45
|
-
} from "./integrations.js";
|
|
46
|
-
|
|
47
|
-
const vaultApiFetchMock = vi.mocked(vaultApiFetch);
|
|
48
|
-
|
|
49
|
-
function jsonResponse(body: unknown, status = 200): Response {
|
|
50
|
-
return {
|
|
51
|
-
ok: status >= 200 && status < 300,
|
|
52
|
-
status,
|
|
53
|
-
json: async () => body,
|
|
54
|
-
} as unknown as Response;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function connectionsResponse() {
|
|
58
|
-
return jsonResponse({
|
|
59
|
-
connections: [
|
|
60
|
-
{
|
|
61
|
-
id: "acct_factory_linear",
|
|
62
|
-
provider: "factory:linear",
|
|
63
|
-
status: "connected",
|
|
64
|
-
writePolicy: "confirm",
|
|
65
|
-
installation: { displayName: "Linear MCP server", status: "installed" },
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function gatewayTextResponse(payload: unknown) {
|
|
72
|
-
return jsonResponse({
|
|
73
|
-
jsonrpc: "2.0",
|
|
74
|
-
id: "x",
|
|
75
|
-
result: { content: [{ type: "text", text: JSON.stringify(payload) }] },
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function runCli(argv: string[]): Promise<void> {
|
|
80
|
-
const program = new Command();
|
|
81
|
-
program.exitOverride();
|
|
82
|
-
registerIntegrationsCommand(program);
|
|
83
|
-
await program.parseAsync(["node", "hq", ...argv]);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let logSpy: MockInstance;
|
|
87
|
-
beforeEach(() => {
|
|
88
|
-
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
89
|
-
});
|
|
90
|
-
afterEach(() => {
|
|
91
|
-
vi.clearAllMocks();
|
|
92
|
-
logSpy.mockRestore();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
function logged(): string {
|
|
96
|
-
return logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
describe("toolPrefixForProvider", () => {
|
|
100
|
-
it("strips the factory: prefix and normalizes to the gateway's tool prefix", () => {
|
|
101
|
-
expect(toolPrefixForProvider("factory:linear")).toBe("linear");
|
|
102
|
-
expect(toolPrefixForProvider("factory:google-workspace")).toBe("google.workspace");
|
|
103
|
-
expect(toolPrefixForProvider("Linear")).toBe("linear");
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("selectConnection", () => {
|
|
108
|
-
const linear = {
|
|
109
|
-
id: "acct_1",
|
|
110
|
-
provider: "factory:linear",
|
|
111
|
-
status: "connected",
|
|
112
|
-
};
|
|
113
|
-
const notion = {
|
|
114
|
-
id: "acct_2",
|
|
115
|
-
provider: "factory:notion",
|
|
116
|
-
status: "connected",
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
it("matches --provider against the bare factory slug", () => {
|
|
120
|
-
expect(selectConnection([linear, notion], { provider: "linear" })).toBe(linear);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("defaults to the single active connection", () => {
|
|
124
|
-
expect(selectConnection([linear], {})).toBe(linear);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("guides on ambiguity and on unknown providers", () => {
|
|
128
|
-
expect(() => selectConnection([linear, notion], {})).toThrow(/--provider/);
|
|
129
|
-
expect(() => selectConnection([linear], { provider: "jira" })).toThrow(
|
|
130
|
-
/Connected: linear/,
|
|
131
|
-
);
|
|
132
|
-
expect(() => selectConnection([], {})).toThrow(/No connected apps yet/);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("resolves an explicit connection id even when revoked", () => {
|
|
136
|
-
const revoked = { ...linear, status: "revoked" };
|
|
137
|
-
expect(selectConnection([revoked], { connection: "acct_1" })).toBe(revoked);
|
|
138
|
-
expect(() => selectConnection([revoked], { connection: "acct_9" })).toThrow(
|
|
139
|
-
IntegrationsCliError,
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe("unwrapGatewayResult + queuedOutcome", () => {
|
|
145
|
-
it("unwraps MCP text content into the inner JSON payload", () => {
|
|
146
|
-
expect(
|
|
147
|
-
unwrapGatewayResult({
|
|
148
|
-
content: [{ type: "text", text: '{"tools":[{"name":"list_issues"}]}' }],
|
|
149
|
-
}),
|
|
150
|
-
).toEqual({ tools: [{ name: "list_issues" }] });
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("falls back to raw text / raw result on non-JSON shapes", () => {
|
|
154
|
-
expect(
|
|
155
|
-
unwrapGatewayResult({ content: [{ type: "text", text: "plain" }] }),
|
|
156
|
-
).toBe("plain");
|
|
157
|
-
expect(unwrapGatewayResult({ other: 1 })).toEqual({ other: 1 });
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("detects the queued-for-approval outcome", () => {
|
|
161
|
-
expect(
|
|
162
|
-
queuedOutcome({ queuedForApproval: true, queueId: "cq_1" }),
|
|
163
|
-
).toMatchObject({ queueId: "cq_1" });
|
|
164
|
-
expect(queuedOutcome({ issues: [] })).toBeNull();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe("hq integrations tools", () => {
|
|
169
|
-
it("composes the read tool name from the connection's provider and lists upstream tools", async () => {
|
|
170
|
-
vaultApiFetchMock
|
|
171
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
172
|
-
.mockResolvedValueOnce(
|
|
173
|
-
gatewayTextResponse({
|
|
174
|
-
tools: [{ name: "list_issues", title: "List issues" }],
|
|
175
|
-
}),
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
await runCli(["integrations", "tools", "--provider", "linear"]);
|
|
179
|
-
|
|
180
|
-
const gatewayCall = vaultApiFetchMock.mock.calls[1]![0];
|
|
181
|
-
expect(gatewayCall).toMatchObject({
|
|
182
|
-
path: "/v1/integrations/mcp",
|
|
183
|
-
method: "POST",
|
|
184
|
-
});
|
|
185
|
-
expect(gatewayCall.body).toMatchObject({
|
|
186
|
-
method: "tools/call",
|
|
187
|
-
params: {
|
|
188
|
-
companyUid: "cmp_acme",
|
|
189
|
-
name: "linear.mcp.tools.list",
|
|
190
|
-
arguments: { companyUid: "cmp_acme", connectionId: "acct_factory_linear" },
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
expect(logged()).toContain("list_issues");
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe("hq integrations call", () => {
|
|
198
|
-
it("sends the upstream tool + args + idempotency key and prints the result", async () => {
|
|
199
|
-
vaultApiFetchMock
|
|
200
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
201
|
-
.mockResolvedValueOnce(gatewayTextResponse({ issues: [{ id: "DEV-1" }] }));
|
|
202
|
-
|
|
203
|
-
await runCli([
|
|
204
|
-
"integrations",
|
|
205
|
-
"call",
|
|
206
|
-
"list_issues",
|
|
207
|
-
"--provider",
|
|
208
|
-
"linear",
|
|
209
|
-
"--args",
|
|
210
|
-
'{"assignee":"me"}',
|
|
211
|
-
"--idempotency-key",
|
|
212
|
-
"stable-key",
|
|
213
|
-
]);
|
|
214
|
-
|
|
215
|
-
const gatewayCall = vaultApiFetchMock.mock.calls[1]![0];
|
|
216
|
-
expect(gatewayCall.body).toMatchObject({
|
|
217
|
-
params: {
|
|
218
|
-
name: "linear.mcp.tools.call",
|
|
219
|
-
arguments: {
|
|
220
|
-
toolName: "list_issues",
|
|
221
|
-
arguments: { assignee: "me" },
|
|
222
|
-
idempotencyKey: "stable-key",
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
expect(logged()).toContain("DEV-1");
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("surfaces a queued outcome with the exact approve command", async () => {
|
|
230
|
-
vaultApiFetchMock
|
|
231
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
232
|
-
.mockResolvedValueOnce(
|
|
233
|
-
gatewayTextResponse({
|
|
234
|
-
queuedForApproval: true,
|
|
235
|
-
status: "queued",
|
|
236
|
-
queueId: "cq_123",
|
|
237
|
-
connectionId: "acct_factory_linear",
|
|
238
|
-
}),
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
await runCli([
|
|
242
|
-
"integrations",
|
|
243
|
-
"call",
|
|
244
|
-
"save_issue",
|
|
245
|
-
"--provider",
|
|
246
|
-
"linear",
|
|
247
|
-
"--args",
|
|
248
|
-
"{}",
|
|
249
|
-
]);
|
|
250
|
-
|
|
251
|
-
const output = logged();
|
|
252
|
-
expect(output).toContain("Queued for approval");
|
|
253
|
-
expect(output).toContain("hq integrations approve cq_123 --provider linear");
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it("rejects non-object --args with a usage hint", async () => {
|
|
257
|
-
await expect(
|
|
258
|
-
runCli(["integrations", "call", "t", "--provider", "linear", "--args", "[1]"]),
|
|
259
|
-
).rejects.toThrow(/--args must be a JSON object/);
|
|
260
|
-
expect(vaultApiFetchMock).not.toHaveBeenCalled();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it("marks non-object --args as expected", async () => {
|
|
264
|
-
try {
|
|
265
|
-
await runCli(["integrations", "call", "t", "--provider", "linear", "--args", "[1]"]);
|
|
266
|
-
throw new Error("expected runCli to throw");
|
|
267
|
-
} catch (err) {
|
|
268
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
269
|
-
expect((err as IntegrationsCliError).expected).toBe(true);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
describe("hq integrations approve", () => {
|
|
275
|
-
it("posts the decision to the confirm route and prints the unwrapped result", async () => {
|
|
276
|
-
vaultApiFetchMock
|
|
277
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
278
|
-
.mockResolvedValueOnce(
|
|
279
|
-
jsonResponse({
|
|
280
|
-
status: "resumed",
|
|
281
|
-
result: { content: [{ type: "text", text: '{"issues":[]}' }] },
|
|
282
|
-
}),
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
await runCli(["integrations", "approve", "cq_123", "--provider", "linear"]);
|
|
286
|
-
|
|
287
|
-
const confirmCall = vaultApiFetchMock.mock.calls[1]![0];
|
|
288
|
-
expect(confirmCall).toMatchObject({
|
|
289
|
-
path: "/v1/integrations/confirm/cq_123/approve",
|
|
290
|
-
method: "POST",
|
|
291
|
-
body: { companyUid: "cmp_acme", connectionId: "acct_factory_linear" },
|
|
292
|
-
});
|
|
293
|
-
expect(logged()).toContain("Approved");
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
describe("expected integrations errors", () => {
|
|
298
|
-
const linear = {
|
|
299
|
-
id: "acct_1",
|
|
300
|
-
provider: "factory:linear",
|
|
301
|
-
status: "connected",
|
|
302
|
-
};
|
|
303
|
-
const notion = {
|
|
304
|
-
id: "acct_2",
|
|
305
|
-
provider: "factory:notion",
|
|
306
|
-
status: "connected",
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
it("marks a non-owner 403 approve as expected (skips Sentry) and preserves the message", async () => {
|
|
310
|
-
vaultApiFetchMock
|
|
311
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
312
|
-
.mockResolvedValueOnce(
|
|
313
|
-
jsonResponse(
|
|
314
|
-
{ error: "Only a company owner can approve or reject queued integration writes" },
|
|
315
|
-
403,
|
|
316
|
-
),
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
await runCli(["integrations", "approve", "cq_123", "--provider", "linear"]);
|
|
321
|
-
throw new Error("expected runCli to throw");
|
|
322
|
-
} catch (err) {
|
|
323
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
324
|
-
expect((err as IntegrationsCliError).expected).toBe(true);
|
|
325
|
-
expect((err as Error).message).toContain("Only a company owner");
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("still captures a genuine server 500 on approve (expected === false)", async () => {
|
|
330
|
-
vaultApiFetchMock
|
|
331
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
332
|
-
.mockResolvedValueOnce(jsonResponse({ error: "boom" }, 500));
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
await runCli(["integrations", "approve", "cq_123", "--provider", "linear"]);
|
|
336
|
-
throw new Error("expected runCli to throw");
|
|
337
|
-
} catch (err) {
|
|
338
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
339
|
-
expect((err as IntegrationsCliError).expected).toBe(false);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it("marks reject 403 as expected too", async () => {
|
|
344
|
-
vaultApiFetchMock
|
|
345
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
346
|
-
.mockResolvedValueOnce(
|
|
347
|
-
jsonResponse(
|
|
348
|
-
{ error: "Only a company owner can approve or reject queued integration writes" },
|
|
349
|
-
403,
|
|
350
|
-
),
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
await runCli(["integrations", "reject", "cq_123", "--provider", "linear"]);
|
|
355
|
-
throw new Error("expected runCli to throw");
|
|
356
|
-
} catch (err) {
|
|
357
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
358
|
-
expect((err as IntegrationsCliError).expected).toBe(true);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
expect(vaultApiFetchMock.mock.calls[1]![0].path).toBe(
|
|
362
|
-
"/v1/integrations/confirm/cq_123/reject",
|
|
363
|
-
);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it("marks local connection selection usage errors as expected", () => {
|
|
367
|
-
for (const fn of [
|
|
368
|
-
() => selectConnection([], {}),
|
|
369
|
-
() => selectConnection([linear, notion], {}),
|
|
370
|
-
() => selectConnection([linear], { provider: "jira" }),
|
|
371
|
-
]) {
|
|
372
|
-
try {
|
|
373
|
-
fn();
|
|
374
|
-
throw new Error("expected selectConnection to throw");
|
|
375
|
-
} catch (err) {
|
|
376
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
377
|
-
expect((err as IntegrationsCliError).expected).toBe(true);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
// HQ-CLI-9: `hq integrations call get_board_info --provider monday …` hit a 401
|
|
384
|
-
// on POST /v1/integrations/mcp while the caller's HQ session was rejected. The
|
|
385
|
-
// old `callGateway` threw a plain, opaque `IntegrationsCliError("Integration
|
|
386
|
-
// gateway request failed (HTTP 401).")` that shipped to Sentry as an
|
|
387
|
-
// unactionable fatal. A gateway 401 is an expired/missing session — the same
|
|
388
|
-
// expected auth state the vault company-resolution paths already raise as
|
|
389
|
-
// `AuthError` (HQ-CLI-8). The fix routes every integration-gateway 401 through
|
|
390
|
-
// that typed AuthError so the user gets one actionable "run `hq login`" message
|
|
391
|
-
// and Sentry is skipped, while genuine 4xx/5xx faults keep their behavior.
|
|
392
|
-
describe("integration gateway 401 → AuthError (HQ-CLI-9)", () => {
|
|
393
|
-
it("throws an actionable AuthError (not an opaque IntegrationsCliError) when callGateway 401s", async () => {
|
|
394
|
-
vaultApiFetchMock
|
|
395
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
396
|
-
.mockResolvedValueOnce(jsonResponse({ error: "Unauthorized" }, 401));
|
|
397
|
-
|
|
398
|
-
const err = await runCli([
|
|
399
|
-
"integrations",
|
|
400
|
-
"call",
|
|
401
|
-
"get_board_info",
|
|
402
|
-
"--provider",
|
|
403
|
-
"linear",
|
|
404
|
-
"--args",
|
|
405
|
-
"{}",
|
|
406
|
-
]).then(
|
|
407
|
-
() => {
|
|
408
|
-
throw new Error("expected runCli to throw");
|
|
409
|
-
},
|
|
410
|
-
(e: unknown) => e,
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
expect(isAuthError(err)).toBe(true);
|
|
414
|
-
expect(err).toBeInstanceOf(AuthError);
|
|
415
|
-
expect((err as Error).message).toMatch(/hq login/);
|
|
416
|
-
// The gateway request was actually attempted (connections + gateway call).
|
|
417
|
-
expect(vaultApiFetchMock.mock.calls[1]![0].path).toBe("/v1/integrations/mcp");
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it("throws an AuthError when the admin (fetchConnections) call 401s", async () => {
|
|
421
|
-
vaultApiFetchMock.mockResolvedValueOnce(jsonResponse({ error: "Unauthorized" }, 401));
|
|
422
|
-
|
|
423
|
-
const err = await runCli(["integrations", "list"]).then(
|
|
424
|
-
() => {
|
|
425
|
-
throw new Error("expected runCli to throw");
|
|
426
|
-
},
|
|
427
|
-
(e: unknown) => e,
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
expect(isAuthError(err)).toBe(true);
|
|
431
|
-
expect((err as Error).message).toMatch(/hq login/);
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
it("throws an AuthError when an approve confirm call 401s", async () => {
|
|
435
|
-
vaultApiFetchMock
|
|
436
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
437
|
-
.mockResolvedValueOnce(jsonResponse({ error: "Unauthorized" }, 401));
|
|
438
|
-
|
|
439
|
-
const err = await runCli([
|
|
440
|
-
"integrations",
|
|
441
|
-
"approve",
|
|
442
|
-
"cq_123",
|
|
443
|
-
"--provider",
|
|
444
|
-
"linear",
|
|
445
|
-
]).then(
|
|
446
|
-
() => {
|
|
447
|
-
throw new Error("expected runCli to throw");
|
|
448
|
-
},
|
|
449
|
-
(e: unknown) => e,
|
|
450
|
-
);
|
|
451
|
-
|
|
452
|
-
expect(isAuthError(err)).toBe(true);
|
|
453
|
-
expect((err as Error).message).toMatch(/hq login/);
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// Scope guards: only a 401 becomes an AuthError. A genuine server 500 still
|
|
457
|
-
// reports (expected === false), and a provider-level JSON-RPC error (HTTP 200
|
|
458
|
-
// with `message.error`) stays an IntegrationsCliError — so real faults and
|
|
459
|
-
// upstream tool errors are never misclassified as an auth state.
|
|
460
|
-
it("keeps a gateway 500 as a reporting IntegrationsCliError (not an AuthError)", async () => {
|
|
461
|
-
vaultApiFetchMock
|
|
462
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
463
|
-
.mockResolvedValueOnce(jsonResponse({ error: "boom" }, 500));
|
|
464
|
-
|
|
465
|
-
const err = await runCli([
|
|
466
|
-
"integrations",
|
|
467
|
-
"call",
|
|
468
|
-
"get_board_info",
|
|
469
|
-
"--provider",
|
|
470
|
-
"linear",
|
|
471
|
-
"--args",
|
|
472
|
-
"{}",
|
|
473
|
-
]).then(
|
|
474
|
-
() => {
|
|
475
|
-
throw new Error("expected runCli to throw");
|
|
476
|
-
},
|
|
477
|
-
(e: unknown) => e,
|
|
478
|
-
);
|
|
479
|
-
|
|
480
|
-
expect(isAuthError(err)).toBe(false);
|
|
481
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
482
|
-
expect((err as IntegrationsCliError).expected).toBe(false);
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
it("keeps an upstream provider error (HTTP 200 message.error) as an IntegrationsCliError", async () => {
|
|
486
|
-
vaultApiFetchMock
|
|
487
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
488
|
-
.mockResolvedValueOnce(
|
|
489
|
-
jsonResponse({
|
|
490
|
-
jsonrpc: "2.0",
|
|
491
|
-
id: "x",
|
|
492
|
-
error: { code: -32050, message: "monday rejected the board id" },
|
|
493
|
-
}),
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
const err = await runCli([
|
|
497
|
-
"integrations",
|
|
498
|
-
"call",
|
|
499
|
-
"get_board_info",
|
|
500
|
-
"--provider",
|
|
501
|
-
"linear",
|
|
502
|
-
"--args",
|
|
503
|
-
"{}",
|
|
504
|
-
]).then(
|
|
505
|
-
() => {
|
|
506
|
-
throw new Error("expected runCli to throw");
|
|
507
|
-
},
|
|
508
|
-
(e: unknown) => e,
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
expect(isAuthError(err)).toBe(false);
|
|
512
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
513
|
-
expect((err as Error).message).toMatch(/monday rejected the board id/);
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
// HQ-CLI-B: `hq integrations tools|call … --provider …` against a connection the
|
|
518
|
-
// caller has NOT been granted returned HTTP 200 with a JSON-RPC error
|
|
519
|
-
// { code: -32003, message: "You do not have access to this integration. Ask its
|
|
520
|
-
// owner to share it with you.", data: { code: "IntegrationAccessDenied" } }.
|
|
521
|
-
// `callGateway` threw an IntegrationsCliError WITHOUT `expected`, so it defaulted
|
|
522
|
-
// to `expected === false` and the top-level handler shipped a GOVERNED, correct
|
|
523
|
-
// access denial to Sentry as an error-level fatal — 40 identical, unfixable
|
|
524
|
-
// events for one user. A caller-side JSON-RPC code (UNAUTHORIZED / INVALID_PARAMS)
|
|
525
|
-
// is the analog of a client 4xx and must be printed to the user, not captured;
|
|
526
|
-
// genuine server/provider/protocol faults must still report.
|
|
527
|
-
describe("gateway JSON-RPC error classification (HQ-CLI-B)", () => {
|
|
528
|
-
function gatewayJsonRpcError(code: number | undefined, message: string, data?: unknown) {
|
|
529
|
-
return jsonResponse({
|
|
530
|
-
jsonrpc: "2.0",
|
|
531
|
-
id: "x",
|
|
532
|
-
error: { ...(code == null ? {} : { code }), message, ...(data ? { data } : {}) },
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
async function runGatewayCall(): Promise<unknown> {
|
|
537
|
-
return runCli([
|
|
538
|
-
"integrations",
|
|
539
|
-
"call",
|
|
540
|
-
"get_board_info",
|
|
541
|
-
"--provider",
|
|
542
|
-
"linear",
|
|
543
|
-
"--args",
|
|
544
|
-
"{}",
|
|
545
|
-
]).then(
|
|
546
|
-
() => {
|
|
547
|
-
throw new Error("expected runCli to throw");
|
|
548
|
-
},
|
|
549
|
-
(e: unknown) => e,
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
it("marks the access-denied gateway error (-32003) as expected and preserves its actionable message", async () => {
|
|
554
|
-
vaultApiFetchMock
|
|
555
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
556
|
-
.mockResolvedValueOnce(
|
|
557
|
-
gatewayJsonRpcError(
|
|
558
|
-
-32003,
|
|
559
|
-
"You do not have access to this integration. Ask its owner to share it with you.",
|
|
560
|
-
{ code: "IntegrationAccessDenied" },
|
|
561
|
-
),
|
|
562
|
-
);
|
|
563
|
-
|
|
564
|
-
const err = await runGatewayCall();
|
|
565
|
-
|
|
566
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
567
|
-
// The regression: this was `false` and flooded Sentry with a governed denial.
|
|
568
|
-
expect((err as IntegrationsCliError).expected).toBe(true);
|
|
569
|
-
// A connection-level denial is NOT an expired session — never an AuthError.
|
|
570
|
-
expect(isAuthError(err)).toBe(false);
|
|
571
|
-
expect((err as Error).message).toContain("do not have access to this integration");
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it("also marks access-denied as expected on the `tools` path (same callGateway seam)", async () => {
|
|
575
|
-
vaultApiFetchMock
|
|
576
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
577
|
-
.mockResolvedValueOnce(
|
|
578
|
-
gatewayJsonRpcError(
|
|
579
|
-
-32003,
|
|
580
|
-
"You do not have access to this integration. Ask its owner to share it with you.",
|
|
581
|
-
),
|
|
582
|
-
);
|
|
583
|
-
|
|
584
|
-
const err = await runCli(["integrations", "tools", "--provider", "linear"]).then(
|
|
585
|
-
() => {
|
|
586
|
-
throw new Error("expected runCli to throw");
|
|
587
|
-
},
|
|
588
|
-
(e: unknown) => e,
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
592
|
-
expect((err as IntegrationsCliError).expected).toBe(true);
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
it("marks an invalid-params gateway error (-32602) as expected", async () => {
|
|
596
|
-
vaultApiFetchMock
|
|
597
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
598
|
-
.mockResolvedValueOnce(gatewayJsonRpcError(-32602, "Unknown integration tool: get_board_info"));
|
|
599
|
-
|
|
600
|
-
const err = await runGatewayCall();
|
|
601
|
-
|
|
602
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
603
|
-
expect((err as IntegrationsCliError).expected).toBe(true);
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
it("still reports genuine provider/internal/conflict gateway faults (expected === false)", async () => {
|
|
607
|
-
// -32050 PROVIDER_ERROR, -32603 INTERNAL_ERROR, and -32009 CONFLICT (which the
|
|
608
|
-
// gateway raises for a confirm queue being unavailable or an owner
|
|
609
|
-
// notification failing) are real faults that must keep reaching Sentry.
|
|
610
|
-
for (const code of [-32050, -32603, -32009]) {
|
|
611
|
-
vaultApiFetchMock
|
|
612
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
613
|
-
.mockResolvedValueOnce(gatewayJsonRpcError(code, "gateway fault"));
|
|
614
|
-
|
|
615
|
-
const err = await runGatewayCall();
|
|
616
|
-
|
|
617
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
618
|
-
expect((err as IntegrationsCliError).expected).toBe(false);
|
|
619
|
-
vaultApiFetchMock.mockClear();
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
it("reports a gateway error with no JSON-RPC code (unclassified → expected === false)", async () => {
|
|
624
|
-
vaultApiFetchMock
|
|
625
|
-
.mockResolvedValueOnce(connectionsResponse())
|
|
626
|
-
.mockResolvedValueOnce(gatewayJsonRpcError(undefined, "codeless failure"));
|
|
627
|
-
|
|
628
|
-
const err = await runGatewayCall();
|
|
629
|
-
|
|
630
|
-
expect(err).toBeInstanceOf(IntegrationsCliError);
|
|
631
|
-
expect((err as IntegrationsCliError).expected).toBe(false);
|
|
632
|
-
});
|
|
633
|
-
});
|