mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-05 03:51:54 +01:00
merge: reconcile published native prerequisite and current master
Preserve canonical startup ownership, scoped evidence, current Storybook IDs, and the Codex form controls while carrying Claude effort and immutable source bindings. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
48ffca9438
415 files changed
+22501
-15340
No files matched your search
@@ -156,6 +156,114 @@ test.describe.serial("native chat adapter UI", () => {
|
||||
seed = await seedCompanyAndAgent(request);
|
||||
});
|
||||
|
||||
test("Slack: managed workspace setup has two stops, resumes and completes without pasting credentials", async ({ page }) => {
|
||||
const slack = PROVIDERS.find(provider => provider.provider === "slack")!;
|
||||
const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true });
|
||||
await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat`);
|
||||
await selectMaya(page);
|
||||
await expect(page.getByRole("navigation", { name: "Connection setup progress" }).getByRole("button")).toHaveCount(2);
|
||||
await expect(page.getByRole("heading", { name: "Send a message to your agent", exact: true })).toBeVisible();
|
||||
await expect(page.getByLabel("App configuration access token", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByLabel("Signing Secret", { exact: true })).toHaveCount(0);
|
||||
await page.reload();
|
||||
expect(mock.slackCreations).toBe(1); expect(mock.slackInstallations).toBe(1);
|
||||
mock.setWebhookVerified();
|
||||
await expect(page.getByRole("heading", { name: "Success", exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("navigation", { name: "Connection setup progress" }).getByRole("button")).toHaveCount(2);
|
||||
await page.getByRole("button", { name: "Done", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/apps\/chat\/endpoint-slack\/settings$/);
|
||||
});
|
||||
test("Slack: a failed manager refresh reloads workspace authorization before retry", async ({ page }) => {
|
||||
const slack = PROVIDERS.find(provider => provider.provider === "slack")!;
|
||||
await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true });
|
||||
let refreshFailed = false;
|
||||
let authorizationRequests = 0;
|
||||
await page.route("**/chat-slack/setup-options", async route => {
|
||||
if (!refreshFailed) return route.fallback();
|
||||
await route.fulfill({ json: { managedAvailable: true, defaultMethod: "managed", workspaces: [] } });
|
||||
});
|
||||
await page.route("**/slack/managed/provision", async route => {
|
||||
refreshFailed = true;
|
||||
await route.fulfill({ status: 422, json: { error: "Reconnect your Slack workspace.", details: { code: "slack_manager_reauthorize" } } });
|
||||
});
|
||||
await page.route("**/slack/managed/authorize", async route => {
|
||||
authorizationRequests++;
|
||||
await route.fulfill({ json: { authorizationUrl: "https://slack.test/manager-consent", expiresAt: new Date(Date.now() + 600_000).toISOString() } });
|
||||
});
|
||||
await page.route("https://slack.test/manager-consent", route => route.fulfill({ contentType: "text/html", body: "Slack authorization fixture" }));
|
||||
await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`);
|
||||
await expect(page.getByRole("alert")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||
await expect.poll(() => authorizationRequests).toBe(1);
|
||||
await expect(page).toHaveURL("https://slack.test/manager-consent");
|
||||
});
|
||||
test("Slack: every uncertain creation retry needs a fresh confirmation", async ({ page }) => {
|
||||
const slack = PROVIDERS.find(provider => provider.provider === "slack")!;
|
||||
const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true });
|
||||
mock.setSlackUncertain();
|
||||
const attempts: { requestId: string; confirmedNoAppCreated?: boolean }[] = [];
|
||||
await page.route("**/slack/managed/provision", async route => {
|
||||
attempts.push(route.request().postDataJSON());
|
||||
await route.fulfill({ json: {} });
|
||||
});
|
||||
await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`);
|
||||
const confirm = page.getByRole("checkbox", { name: "I checked Slack and no app was created. Create a new app." });
|
||||
const create = page.getByRole("button", { name: "Continue", exact: true });
|
||||
await expect(create).toBeDisabled();
|
||||
await confirm.check(); await create.click();
|
||||
await expect(confirm).not.toBeChecked(); await expect(create).toBeDisabled();
|
||||
expect(attempts).toHaveLength(1); expect(attempts[0]!.confirmedNoAppCreated).toBe(true);
|
||||
await confirm.check(); await create.click();
|
||||
await expect(confirm).not.toBeChecked(); await expect(create).toBeDisabled();
|
||||
expect(attempts).toHaveLength(2); expect(attempts[1]!.requestId).not.toBe(attempts[0]!.requestId);
|
||||
});
|
||||
test("Slack: own-app creation consumes confirmation even when the response is lost", async ({ page }) => {
|
||||
const slack = PROVIDERS.find(provider => provider.provider === "slack")!;
|
||||
const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, automaticSlack: true });
|
||||
mock.setSlackUncertain();
|
||||
let attempts = 0;
|
||||
await page.route("**/slack/registration", async route => {
|
||||
attempts++; expect(route.request().postDataJSON().confirmedNoAppCreated).toBe(true);
|
||||
await route.fulfill({ status: 502, json: { error: "Creation response unavailable" } });
|
||||
});
|
||||
await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`);
|
||||
const confirm = page.getByRole("checkbox");
|
||||
await confirm.check();
|
||||
await page.getByLabel("App configuration access token", { exact: true }).fill("fixture-config-token");
|
||||
await page.getByRole("button", { name: "Create Slack app", exact: true }).click();
|
||||
await expect(page.getByRole("alert")).toBeVisible();
|
||||
await expect(confirm).not.toBeChecked();
|
||||
await expect(page.getByRole("button", { name: "Create Slack app", exact: true })).toBeDisabled();
|
||||
expect(attempts).toBe(1);
|
||||
});
|
||||
test("Slack: temporarily unavailable managed setup can retry in place", async ({ page }) => {
|
||||
const slack = PROVIDERS.find(provider => provider.provider === "slack")!;
|
||||
await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true });
|
||||
let available = false;
|
||||
await page.route("**/chat-slack/setup-options", async route => {
|
||||
if (available) return route.fallback();
|
||||
await route.fulfill({ json: { managedAvailable: false, defaultMethod: "automatic", workspaces: [] } });
|
||||
});
|
||||
await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`);
|
||||
await expect(page.getByText("Managed Slack setup is unavailable. Try again later.")).toBeVisible();
|
||||
available = true; await page.getByRole("button", { name: "Retry", exact: true }).click();
|
||||
await expect(page.getByRole("heading", { name: "Send a message to your agent", exact: true })).toBeVisible();
|
||||
});
|
||||
test("Slack: own-app selection persists before provisioning", async ({ page }) => {
|
||||
const slack = PROVIDERS.find(provider => provider.provider === "slack")!;
|
||||
await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true });
|
||||
await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat`);
|
||||
await page.getByRole("button", { name: "Choose an active agent" }).click();
|
||||
await page.getByRole("button", { name: "Select Maya" }).click();
|
||||
await page.getByText("Advanced", { exact: true }).click();
|
||||
await page.getByRole("button", { name: "Use your own app" }).click();
|
||||
await expect(page.getByRole("heading", { name: "App configuration access token", exact: true })).toBeVisible();
|
||||
await page.reload();
|
||||
await expect(page.getByRole("heading", { name: "App configuration access token", exact: true })).toBeVisible();
|
||||
await page.getByText("Advanced", { exact: true }).click();
|
||||
await page.getByRole("button", { name: "Use managed setup" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Send a message to your agent", exact: true })).toBeVisible();
|
||||
});
|
||||
test("Slack: automatic creation survives refresh, consent, and connection evidence without copying durable secrets", async ({ page }) => {
|
||||
const slack = PROVIDERS.find(provider => provider.provider === "slack")!;
|
||||
const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, automaticSlack: true });
|
||||
@@ -440,6 +548,10 @@ test.describe.serial("native chat adapter UI", () => {
|
||||
).toBeVisible();
|
||||
await expectSetupRail(page, true);
|
||||
await selectMaya(page);
|
||||
if (provider.provider === "slack") {
|
||||
await page.getByText("Advanced", { exact: true }).click();
|
||||
await page.getByRole("button", { name: "Create manually", exact: true }).click();
|
||||
}
|
||||
await expect.poll(() => mock.createdWithAgentId).toBe(seed.agentId);
|
||||
expect(mock.createdWithAgentId).not.toBe(seed.otherAgentId);
|
||||
await expect(
|
||||
@@ -573,18 +685,12 @@ test.describe.serial("native chat adapter UI", () => {
|
||||
page.getByRole("heading", { name: provider.provider === "slack" ? "Allowed Channels" : "Destinations" }),
|
||||
).toBeVisible();
|
||||
if (provider.provider === "slack") {
|
||||
await page.locator("summary").filter({ hasText: "Message in a channel" }).click();
|
||||
await expect(page.getByText("@maya-paperclip you there?", { exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Copy message" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Allowed Channels" })).toBeVisible();
|
||||
await page.locator("summary").filter({ hasText: /^Agent avatar$/ }).click();
|
||||
const avatarSection = page.getByRole("region", { name: "Slack avatar" });
|
||||
await expect(avatarSection.getByRole("link", { name: "Download avatar" })).toBeVisible();
|
||||
await avatarSection.getByText("How to upload in Slack", { exact: true }).click();
|
||||
await expect(avatarSection.getByRole("link", { name: "Open Slack app Settings" })).toBeVisible();
|
||||
const settingsDownloadEvent = page.waitForEvent("download");
|
||||
await avatarSection.getByRole("link", { name: "Download avatar" }).click();
|
||||
expect((await settingsDownloadEvent).suggestedFilename()).toBe("maya-paperclip-avatar.png");
|
||||
await expect(page.getByRole("button", { name: "Send me a test message" })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "Open Slack configuration" })).toBeVisible();
|
||||
await expect(page.getByText("Message in a channel", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText("Agent avatar", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByRole("link", { name: "View message" })).toHaveCount(0);
|
||||
}
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -281,7 +281,7 @@ export function endpointFixture(provider: ProviderCase, seed: Seed) {
|
||||
proactiveDirectMessages: false,
|
||||
},
|
||||
setup: {
|
||||
slackSetupMethod: undefined as "automatic" | "manual" | "existing" | undefined,
|
||||
slackSetupMethod: undefined as "managed" | "automatic" | "manual" | "existing" | undefined,
|
||||
slackRegistration: undefined as SlackRegistrationState | undefined,
|
||||
slackApp: undefined as SlackAppConfiguration | undefined,
|
||||
slackOAuthCallbackUri: "https://paperclip.example.test/api/chat-slack/oauth/callback",
|
||||
@@ -353,10 +353,11 @@ export async function installChatControlPlaneMock(
|
||||
resourceCount = 2,
|
||||
photonShared = false,
|
||||
automaticSlack = false,
|
||||
}: { enableChatConnectors: boolean; enableGitHubReviewBots?: boolean; resourceCount?: number; photonShared?: boolean; automaticSlack?: boolean },
|
||||
managedSlack = false,
|
||||
}: { enableChatConnectors: boolean; enableGitHubReviewBots?: boolean; resourceCount?: number; photonShared?: boolean; automaticSlack?: boolean; managedSlack?: boolean },
|
||||
): Promise<ChatMock> {
|
||||
const endpoint = endpointFixture(provider, seed);
|
||||
if (automaticSlack) endpoint.setup.slackSetupMethod = "automatic";
|
||||
if (automaticSlack || managedSlack) endpoint.setup.slackSetupMethod = managedSlack ? "managed" : "automatic";
|
||||
let slackIdentityLinked = false;
|
||||
let githubAppConnected = false;
|
||||
let githubConfiguration = { revision: 0, configuration: {
|
||||
@@ -510,6 +511,7 @@ export async function installChatControlPlaneMock(
|
||||
assignedAgentId: seed.agentId,
|
||||
});
|
||||
state.createdWithAgentId = String(body.assignedAgentId);
|
||||
if (body.slackSetupMethod) endpoint.setup.slackSetupMethod = body.slackSetupMethod as "managed" | "automatic" | "manual" | "existing";
|
||||
if (provider.provider === "slack" && body.slackApp) {
|
||||
endpoint.setup.slackApp = body.slackApp as SlackAppConfiguration;
|
||||
endpoint.setup.command = endpoint.setup.slackApp.command;
|
||||
@@ -536,7 +538,7 @@ export async function installChatControlPlaneMock(
|
||||
if (method === "PATCH") {
|
||||
const body = bodyOf(route);
|
||||
if (body.slackApp) endpoint.setup.slackApp = body.slackApp as SlackAppConfiguration;
|
||||
if (body.slackSetupMethod) endpoint.setup.slackSetupMethod = body.slackSetupMethod as "automatic" | "manual" | "existing";
|
||||
if (body.slackSetupMethod) endpoint.setup.slackSetupMethod = body.slackSetupMethod as "managed" | "automatic" | "manual" | "existing";
|
||||
if (typeof body.allowDirectMessages === "boolean")
|
||||
state.allowDirectMessages = body.allowDirectMessages;
|
||||
if (typeof body.allowGroupChats === "boolean")
|
||||
@@ -563,6 +565,16 @@ export async function installChatControlPlaneMock(
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname === `/api/companies/${seed.companyId}/chat-slack/setup-options`) {
|
||||
await fulfill(route, { managedAvailable: managedSlack, defaultMethod: managedSlack ? "managed" : "automatic", workspaces: managedSlack ? [{ grantId: "11111111-1111-4111-8111-111111111111", workspaceId: "TE2E", workspaceName: "Paperclip", userId: "UINSTALLER" }] : [] }); return;
|
||||
}
|
||||
if (pathname === `/api/chat-endpoints/${endpoint.id}/slack/managed/provision`) {
|
||||
const body = bodyOf(route);
|
||||
expect(body.grantId).toBe("11111111-1111-4111-8111-111111111111");
|
||||
expect(body).not.toHaveProperty("credentials");
|
||||
state.slackCreations++; state.slackInstallations++;
|
||||
state.setSlackInstalled(); await fulfill(route, {}); return;
|
||||
}
|
||||
if (pathname === `/api/chat-endpoints/${endpoint.id}/slack/registration`) {
|
||||
const body = bodyOf(route);
|
||||
expect(body.credentials).toEqual({ configurationToken: "fixture-config-token" });
|
||||
|
||||
Reference in new issue
Block a user