@agent-native/dispatch 0.13.13 → 0.14.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/dist/actions/delete-destination.d.ts +12 -12
- package/dist/actions/provider-api-audit.d.ts +7 -0
- package/dist/actions/provider-api-audit.d.ts.map +1 -0
- package/dist/actions/provider-api-audit.js +74 -0
- package/dist/actions/provider-api-audit.js.map +1 -0
- package/dist/actions/provider-api-catalog.d.ts +1 -1
- package/dist/actions/provider-api-request.js +10 -0
- package/dist/actions/provider-api-request.js.map +1 -1
- package/dist/actions/send-platform-message.d.ts +1 -0
- package/dist/actions/send-platform-message.js +43 -22
- package/dist/actions/send-platform-message.js.map +1 -1
- package/dist/components/messaging-setup-panel.d.ts.map +1 -1
- package/dist/components/messaging-setup-panel.js +203 -127
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/routes/pages/messaging.js +1 -1
- package/dist/routes/pages/messaging.js.map +1 -1
- package/dist/server/lib/dispatch-integrations.d.ts +7 -1
- package/dist/server/lib/dispatch-integrations.d.ts.map +1 -1
- package/dist/server/lib/dispatch-integrations.js +178 -8
- package/dist/server/lib/dispatch-integrations.js.map +1 -1
- package/dist/server/lib/dispatch-routing.d.ts +6 -0
- package/dist/server/lib/dispatch-routing.d.ts.map +1 -0
- package/dist/server/lib/dispatch-routing.js +30 -0
- package/dist/server/lib/dispatch-routing.js.map +1 -0
- package/dist/server/lib/env-config.d.ts.map +1 -1
- package/dist/server/lib/env-config.js +50 -0
- package/dist/server/lib/env-config.js.map +1 -1
- package/dist/server/lib/onboarding-steps.d.ts.map +1 -1
- package/dist/server/lib/onboarding-steps.js +25 -0
- package/dist/server/lib/onboarding-steps.js.map +1 -1
- package/dist/server/lib/provider-api.d.ts +2 -2
- package/dist/server/lib/provider-api.d.ts.map +1 -1
- package/dist/server/plugins/integrations.d.ts.map +1 -1
- package/dist/server/plugins/integrations.js +5 -2
- package/dist/server/plugins/integrations.js.map +1 -1
- package/package.json +2 -2
- package/src/actions/provider-api-audit.spec.ts +52 -0
- package/src/actions/provider-api-audit.ts +88 -0
- package/src/actions/provider-api-request.ts +10 -0
- package/src/actions/send-platform-message.spec.ts +108 -0
- package/src/actions/send-platform-message.ts +73 -25
- package/src/components/messaging-setup-panel.spec.tsx +191 -0
- package/src/components/messaging-setup-panel.tsx +472 -193
- package/src/routes/pages/messaging.tsx +1 -1
- package/src/server/lib/dispatch-integrations.spec.ts +113 -0
- package/src/server/lib/dispatch-integrations.ts +239 -6
- package/src/server/lib/dispatch-routing.spec.ts +42 -0
- package/src/server/lib/dispatch-routing.ts +42 -0
- package/src/server/lib/env-config.ts +50 -0
- package/src/server/lib/onboarding-steps.ts +32 -0
- package/src/server/plugins/integrations.ts +5 -2
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
DISPATCH_APP_ID,
|
|
8
8
|
executeProviderApiRequest,
|
|
9
9
|
} from "../server/lib/provider-api.js";
|
|
10
|
+
import { buildProviderApiAuditSummary } from "./provider-api-audit.js";
|
|
10
11
|
|
|
11
12
|
const MethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
12
13
|
|
|
@@ -197,6 +198,15 @@ export default defineAction({
|
|
|
197
198
|
),
|
|
198
199
|
}),
|
|
199
200
|
http: false,
|
|
201
|
+
audit: {
|
|
202
|
+
recordInputs: false,
|
|
203
|
+
target: (args) => ({
|
|
204
|
+
type: "provider-api",
|
|
205
|
+
id: String(args.provider),
|
|
206
|
+
visibility: "private",
|
|
207
|
+
}),
|
|
208
|
+
summary: (args) => buildProviderApiAuditSummary(args),
|
|
209
|
+
},
|
|
200
210
|
run: async (args) => {
|
|
201
211
|
if (args.stageAs) {
|
|
202
212
|
const ctx = getCredentialContext();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
getInstallation: vi.fn(),
|
|
5
|
+
resolveTokenBundle: vi.fn(),
|
|
6
|
+
resolveSecret: vi.fn(),
|
|
7
|
+
sendMessageToTarget: vi.fn(),
|
|
8
|
+
getDestinationById: vi.fn(),
|
|
9
|
+
slackAdapter: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("@agent-native/core/integrations", () => ({
|
|
13
|
+
listIntegrationInstallations: mocks.getInstallation,
|
|
14
|
+
resolveIntegrationTokenBundle: mocks.resolveTokenBundle,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("@agent-native/core/server", () => ({
|
|
18
|
+
getRequestUserEmail: () => "member@example.com",
|
|
19
|
+
getRequestOrgId: () => "org-a",
|
|
20
|
+
resolveSecret: mocks.resolveSecret,
|
|
21
|
+
isEmailConfigured: vi.fn(),
|
|
22
|
+
slackAdapter: mocks.slackAdapter,
|
|
23
|
+
telegramAdapter: vi.fn(),
|
|
24
|
+
emailAdapter: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock("../server/lib/dispatch-store.js", () => ({
|
|
28
|
+
getDestinationById: mocks.getDestinationById,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const action = (await import("./send-platform-message.js")).default;
|
|
32
|
+
|
|
33
|
+
describe("send-platform-message tenant scoping", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
mocks.getDestinationById.mockResolvedValue(null);
|
|
37
|
+
mocks.resolveSecret.mockResolvedValue(null);
|
|
38
|
+
mocks.slackAdapter.mockImplementation(
|
|
39
|
+
(options: { resolveBotToken: () => Promise<string | undefined> }) => ({
|
|
40
|
+
formatAgentResponse: (text: string) => ({
|
|
41
|
+
text,
|
|
42
|
+
platformContext: {},
|
|
43
|
+
}),
|
|
44
|
+
sendMessageToTarget: async (...args: unknown[]) => {
|
|
45
|
+
expect(await options.resolveBotToken()).toBe("own-managed-token");
|
|
46
|
+
return mocks.sendMessageToTarget(...args);
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("resolves and uses only the caller org's Slack installation", async () => {
|
|
53
|
+
mocks.getInstallation.mockResolvedValue([
|
|
54
|
+
{
|
|
55
|
+
id: "installation-a",
|
|
56
|
+
installationKey: "team:T1:app:A1",
|
|
57
|
+
teamId: "T1",
|
|
58
|
+
enterpriseId: null,
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
mocks.resolveTokenBundle.mockResolvedValue({
|
|
62
|
+
accessToken: "own-managed-token",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await action.run(
|
|
66
|
+
{
|
|
67
|
+
platform: "slack",
|
|
68
|
+
destination: "C1",
|
|
69
|
+
tenantId: "T1",
|
|
70
|
+
text: "hello",
|
|
71
|
+
},
|
|
72
|
+
{} as never,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(mocks.getInstallation).toHaveBeenCalledWith(
|
|
76
|
+
{
|
|
77
|
+
userEmail: "member@example.com",
|
|
78
|
+
orgId: "org-a",
|
|
79
|
+
},
|
|
80
|
+
"slack",
|
|
81
|
+
);
|
|
82
|
+
expect(mocks.resolveTokenBundle).toHaveBeenCalledWith(
|
|
83
|
+
"slack",
|
|
84
|
+
"team:T1:app:A1",
|
|
85
|
+
);
|
|
86
|
+
expect(mocks.sendMessageToTarget).toHaveBeenCalledOnce();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("does not use another org's managed installation", async () => {
|
|
90
|
+
mocks.getInstallation.mockResolvedValue([]);
|
|
91
|
+
|
|
92
|
+
await expect(
|
|
93
|
+
action.run(
|
|
94
|
+
{
|
|
95
|
+
platform: "slack",
|
|
96
|
+
destination: "C1",
|
|
97
|
+
tenantId: "T-other",
|
|
98
|
+
text: "hello",
|
|
99
|
+
},
|
|
100
|
+
{} as never,
|
|
101
|
+
),
|
|
102
|
+
).rejects.toThrow("That Slack workspace is not connected");
|
|
103
|
+
|
|
104
|
+
expect(mocks.resolveTokenBundle).not.toHaveBeenCalled();
|
|
105
|
+
expect(mocks.slackAdapter).not.toHaveBeenCalled();
|
|
106
|
+
expect(mocks.sendMessageToTarget).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import {
|
|
3
|
+
listIntegrationInstallations,
|
|
4
|
+
resolveIntegrationTokenBundle,
|
|
5
|
+
} from "@agent-native/core/integrations";
|
|
6
|
+
import {
|
|
7
|
+
getRequestOrgId,
|
|
8
|
+
getRequestUserEmail,
|
|
3
9
|
slackAdapter,
|
|
4
10
|
telegramAdapter,
|
|
5
11
|
emailAdapter,
|
|
@@ -8,21 +14,52 @@ import {
|
|
|
8
14
|
} from "@agent-native/core/server";
|
|
9
15
|
import { z } from "zod";
|
|
10
16
|
|
|
11
|
-
import {
|
|
12
|
-
getDestinationById,
|
|
13
|
-
recordAudit,
|
|
14
|
-
} from "../server/lib/dispatch-store.js";
|
|
17
|
+
import { getDestinationById } from "../server/lib/dispatch-store.js";
|
|
15
18
|
|
|
16
|
-
function getAdapter(
|
|
19
|
+
function getAdapter(
|
|
20
|
+
platform: "slack" | "telegram" | "email",
|
|
21
|
+
slackToken?: string,
|
|
22
|
+
) {
|
|
17
23
|
if (platform === "email") return emailAdapter();
|
|
18
|
-
return platform === "slack"
|
|
24
|
+
return platform === "slack"
|
|
25
|
+
? slackAdapter({ resolveBotToken: async () => slackToken })
|
|
26
|
+
: telegramAdapter();
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
async function assertOutboundConfigured(
|
|
22
30
|
platform: "slack" | "telegram" | "email",
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
tenantId?: string,
|
|
32
|
+
): Promise<string | undefined> {
|
|
33
|
+
if (platform === "slack") {
|
|
34
|
+
const userEmail = getRequestUserEmail();
|
|
35
|
+
if (!userEmail) throw new Error("An authenticated user is required");
|
|
36
|
+
const installations = tenantId
|
|
37
|
+
? await listIntegrationInstallations(
|
|
38
|
+
{ userEmail, orgId: getRequestOrgId() ?? null },
|
|
39
|
+
"slack",
|
|
40
|
+
)
|
|
41
|
+
: [];
|
|
42
|
+
const installation =
|
|
43
|
+
installations.find(
|
|
44
|
+
(candidate) =>
|
|
45
|
+
candidate.teamId === tenantId || candidate.enterpriseId === tenantId,
|
|
46
|
+
) ?? null;
|
|
47
|
+
const managed = installation
|
|
48
|
+
? await resolveIntegrationTokenBundle(
|
|
49
|
+
"slack",
|
|
50
|
+
installation.installationKey,
|
|
51
|
+
)
|
|
52
|
+
: null;
|
|
53
|
+
const token =
|
|
54
|
+
managed?.accessToken ?? (await resolveSecret("SLACK_BOT_TOKEN"));
|
|
55
|
+
if (!token) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
tenantId
|
|
58
|
+
? "That Slack workspace is not connected"
|
|
59
|
+
: "Select a Slack workspace for managed outbound messaging",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return token;
|
|
26
63
|
}
|
|
27
64
|
if (platform === "telegram" && !(await resolveSecret("TELEGRAM_BOT_TOKEN"))) {
|
|
28
65
|
throw new Error("Telegram outbound messaging is not configured");
|
|
@@ -45,9 +82,29 @@ export default defineAction({
|
|
|
45
82
|
destinationId: z.string().optional().describe("Saved destination id"),
|
|
46
83
|
destination: z.string().optional().describe("Raw platform destination id"),
|
|
47
84
|
threadRef: z.string().optional().describe("Optional thread reference"),
|
|
85
|
+
tenantId: z
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Slack workspace/team id for managed installations"),
|
|
48
89
|
text: z.string().describe("Message to send"),
|
|
49
90
|
}),
|
|
50
|
-
|
|
91
|
+
audit: {
|
|
92
|
+
recordInputs: false,
|
|
93
|
+
target: (args) => ({
|
|
94
|
+
type: "destination",
|
|
95
|
+
id: args.destinationId || args.destination || "unknown",
|
|
96
|
+
visibility: "private",
|
|
97
|
+
}),
|
|
98
|
+
summary: (args) => `Sent proactive ${args.platform || "saved"} message`,
|
|
99
|
+
},
|
|
100
|
+
run: async ({
|
|
101
|
+
platform,
|
|
102
|
+
destinationId,
|
|
103
|
+
destination,
|
|
104
|
+
threadRef,
|
|
105
|
+
tenantId,
|
|
106
|
+
text,
|
|
107
|
+
}) => {
|
|
51
108
|
const saved = destinationId
|
|
52
109
|
? await getDestinationById(destinationId)
|
|
53
110
|
: null;
|
|
@@ -63,9 +120,12 @@ export default defineAction({
|
|
|
63
120
|
throw new Error("A platform and destination are required");
|
|
64
121
|
}
|
|
65
122
|
|
|
66
|
-
await assertOutboundConfigured(
|
|
123
|
+
const slackToken = await assertOutboundConfigured(
|
|
124
|
+
resolvedPlatform,
|
|
125
|
+
tenantId,
|
|
126
|
+
);
|
|
67
127
|
|
|
68
|
-
const adapter = getAdapter(resolvedPlatform);
|
|
128
|
+
const adapter = getAdapter(resolvedPlatform, slackToken);
|
|
69
129
|
if (!adapter.sendMessageToTarget) {
|
|
70
130
|
throw new Error(
|
|
71
131
|
`Platform ${resolvedPlatform} does not support proactive outbound messaging`,
|
|
@@ -76,19 +136,7 @@ export default defineAction({
|
|
|
76
136
|
destination: resolvedDestination,
|
|
77
137
|
threadRef: resolvedThreadRef,
|
|
78
138
|
label: saved?.name || undefined,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
await recordAudit({
|
|
82
|
-
action: "message.sent",
|
|
83
|
-
targetType: "destination",
|
|
84
|
-
targetId: destinationId || resolvedDestination,
|
|
85
|
-
summary: `Sent proactive ${resolvedPlatform} message${saved?.name ? ` to ${saved.name}` : ""}`,
|
|
86
|
-
metadata: {
|
|
87
|
-
platform: resolvedPlatform,
|
|
88
|
-
destination: resolvedDestination,
|
|
89
|
-
threadRef: resolvedThreadRef,
|
|
90
|
-
text,
|
|
91
|
-
},
|
|
139
|
+
tenantId,
|
|
92
140
|
});
|
|
93
141
|
|
|
94
142
|
return {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import React, { act } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { MessagingSetupPanel } from "./messaging-setup-panel";
|
|
7
|
+
import { TooltipProvider } from "./ui/tooltip";
|
|
8
|
+
|
|
9
|
+
const clientState = vi.hoisted(() => ({
|
|
10
|
+
statuses: [] as any[],
|
|
11
|
+
envStatuses: [] as any[],
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock("@agent-native/core/client", () => ({
|
|
15
|
+
disconnectManagedIntegrationInstallation: vi.fn(() => Promise.resolve()),
|
|
16
|
+
listManagedIntegrationBudgets: vi.fn(() => Promise.resolve([])),
|
|
17
|
+
listManagedIntegrationInstallations: vi.fn(() => Promise.resolve([])),
|
|
18
|
+
listManagedIntegrationScopes: vi.fn(() => Promise.resolve([])),
|
|
19
|
+
listIntegrationStatuses: vi.fn(() => Promise.resolve(clientState.statuses)),
|
|
20
|
+
listIntegrationEnvStatuses: vi.fn(() =>
|
|
21
|
+
Promise.resolve(clientState.envStatuses),
|
|
22
|
+
),
|
|
23
|
+
managedIntegrationOAuthUrl: vi.fn(
|
|
24
|
+
(platform: string) => `/_agent-native/integrations/${platform}/oauth/start`,
|
|
25
|
+
),
|
|
26
|
+
managedSlackAgentManifestUrl: vi.fn(
|
|
27
|
+
() => "/_agent-native/integrations/slack/manifest",
|
|
28
|
+
),
|
|
29
|
+
saveIntegrationEnvVars: vi.fn(),
|
|
30
|
+
saveManagedIntegrationBudget: vi.fn(() => Promise.resolve()),
|
|
31
|
+
saveManagedIntegrationScope: vi.fn(() => Promise.resolve()),
|
|
32
|
+
setIntegrationEnabled: vi.fn(),
|
|
33
|
+
setupIntegration: vi.fn(),
|
|
34
|
+
testManagedIntegrationInstallation: vi.fn(() => Promise.resolve()),
|
|
35
|
+
useFormatters: () => ({
|
|
36
|
+
formatDate: (value: Date | number | string) =>
|
|
37
|
+
new Date(value).toLocaleDateString("en-US"),
|
|
38
|
+
}),
|
|
39
|
+
useT: () => (key: string) =>
|
|
40
|
+
(
|
|
41
|
+
({
|
|
42
|
+
"messaging.managed.agentManifest": "Agent manifest",
|
|
43
|
+
"messaging.managed.agentManifestDescription":
|
|
44
|
+
"The Agent manifest enables Slack's Agent view and direct messages.",
|
|
45
|
+
"messaging.managed.addToSlack": "Add to Slack",
|
|
46
|
+
"messaging.managed.requiredCredentials":
|
|
47
|
+
"Save the required Slack app credentials below to enable Add to Slack.",
|
|
48
|
+
}) as Record<string, string>
|
|
49
|
+
)[key] ?? key,
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
vi.mock("@agent-native/core/integrations", () => ({
|
|
53
|
+
listBuiltInChannelIntegrations: () => [
|
|
54
|
+
{
|
|
55
|
+
id: "slack",
|
|
56
|
+
name: "Slack",
|
|
57
|
+
iconKey: "slack",
|
|
58
|
+
description: "Slack description",
|
|
59
|
+
documentation: { href: "/docs/messaging#slack" },
|
|
60
|
+
setup: { steps: ["Create a Slack app."] },
|
|
61
|
+
credentialRequirements: [
|
|
62
|
+
{ key: "SLACK_BOT_TOKEN", label: "Slack Bot Token", required: true },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "email",
|
|
67
|
+
name: "Email",
|
|
68
|
+
iconKey: "email",
|
|
69
|
+
description: "Email description",
|
|
70
|
+
documentation: { href: "/docs/messaging#email" },
|
|
71
|
+
setup: { steps: ["Choose a provider."] },
|
|
72
|
+
credentialRequirements: [
|
|
73
|
+
{
|
|
74
|
+
key: "RESEND_API_KEY",
|
|
75
|
+
label: "Resend API Key",
|
|
76
|
+
required: true,
|
|
77
|
+
alternativeGroup: "email-provider",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "SENDGRID_API_KEY",
|
|
81
|
+
label: "SendGrid API Key",
|
|
82
|
+
required: true,
|
|
83
|
+
alternativeGroup: "email-provider",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
describe("MessagingSetupPanel", () => {
|
|
91
|
+
let container: HTMLDivElement;
|
|
92
|
+
let root: Root;
|
|
93
|
+
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
96
|
+
clientState.statuses = [];
|
|
97
|
+
clientState.envStatuses = [];
|
|
98
|
+
container = document.createElement("div");
|
|
99
|
+
document.body.appendChild(container);
|
|
100
|
+
root = createRoot(container);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
act(() => root.unmount());
|
|
105
|
+
container.remove();
|
|
106
|
+
vi.unstubAllGlobals();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("renders catalog-backed channel cards", async () => {
|
|
110
|
+
await act(async () => {
|
|
111
|
+
root.render(
|
|
112
|
+
<TooltipProvider>
|
|
113
|
+
<MessagingSetupPanel />
|
|
114
|
+
</TooltipProvider>,
|
|
115
|
+
);
|
|
116
|
+
await Promise.resolve();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(container.textContent).toContain("Slack");
|
|
120
|
+
expect(container.textContent).toContain("Slack description");
|
|
121
|
+
expect(container.textContent).toContain("Email");
|
|
122
|
+
expect(container.textContent).toContain("Email description");
|
|
123
|
+
expect(container.textContent).not.toContain("Discord");
|
|
124
|
+
expect(
|
|
125
|
+
container.querySelector(
|
|
126
|
+
'a[href="/_agent-native/integrations/slack/manifest"]',
|
|
127
|
+
)?.textContent,
|
|
128
|
+
).toContain("Agent manifest");
|
|
129
|
+
expect(container.textContent).toContain(
|
|
130
|
+
"enables Slack's Agent view and direct messages",
|
|
131
|
+
);
|
|
132
|
+
expect(
|
|
133
|
+
container.querySelector(
|
|
134
|
+
'a[href="/_agent-native/integrations/slack/oauth/start"]',
|
|
135
|
+
),
|
|
136
|
+
).toBeNull();
|
|
137
|
+
expect(
|
|
138
|
+
[...container.querySelectorAll("button")].find((button) =>
|
|
139
|
+
button.textContent?.includes("Add to Slack"),
|
|
140
|
+
)?.disabled,
|
|
141
|
+
).toBe(true);
|
|
142
|
+
expect(container.textContent).toContain(
|
|
143
|
+
"Save the required Slack app credentials below",
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("shows connected and alternative credential states", async () => {
|
|
148
|
+
clientState.statuses = [
|
|
149
|
+
{ platform: "slack", label: "Slack", configured: true, enabled: true },
|
|
150
|
+
];
|
|
151
|
+
clientState.envStatuses = [
|
|
152
|
+
{
|
|
153
|
+
key: "SLACK_BOT_TOKEN",
|
|
154
|
+
label: "Slack Bot Token",
|
|
155
|
+
required: true,
|
|
156
|
+
configured: true,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: "RESEND_API_KEY",
|
|
160
|
+
label: "Resend API Key",
|
|
161
|
+
required: true,
|
|
162
|
+
configured: true,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
key: "SENDGRID_API_KEY",
|
|
166
|
+
label: "SendGrid API Key",
|
|
167
|
+
required: true,
|
|
168
|
+
configured: false,
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
await act(async () => {
|
|
173
|
+
root.render(
|
|
174
|
+
<TooltipProvider>
|
|
175
|
+
<MessagingSetupPanel />
|
|
176
|
+
</TooltipProvider>,
|
|
177
|
+
);
|
|
178
|
+
await Promise.resolve();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(container.textContent).toContain("Connected");
|
|
182
|
+
expect(container.textContent).toContain("Saved");
|
|
183
|
+
expect(
|
|
184
|
+
container.querySelector(
|
|
185
|
+
'a[href="/_agent-native/integrations/slack/oauth/start"]',
|
|
186
|
+
),
|
|
187
|
+
).not.toBeNull();
|
|
188
|
+
expect(container.querySelectorAll("button").length).toBeGreaterThan(0);
|
|
189
|
+
expect(container.textContent).not.toContain("Save credentials");
|
|
190
|
+
});
|
|
191
|
+
});
|