mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Add dormant provider contracts, qualified patched adapters, tenant-scoped persistence and lifecycle ownership without activating chat routes. Preserve the experimental integration as dependent PR #13038. Co-Authored-By: Paperclip <noreply@paperclip.ing>
235 lines
7.1 KiB
TypeScript
235 lines
7.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
discordMarkdownRequiresAttachment,
|
|
listDiscordBotChannels,
|
|
verifyDiscordBot,
|
|
} from "./chat-discord.js";
|
|
|
|
const applicationId = "123456789012345678";
|
|
const guildId = "1457808928258658549";
|
|
|
|
function json(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
}
|
|
|
|
function fetchFixture(
|
|
routes: Record<string, unknown | (() => Response)>,
|
|
): typeof globalThis.fetch {
|
|
return vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
expect(init?.headers).toEqual({ authorization: "Bot discord-secret" });
|
|
const url = new URL(String(input));
|
|
const route = routes[url.pathname];
|
|
if (route === undefined) return json({ message: "missing fixture" }, 404);
|
|
return typeof route === "function" ? route() : json(route);
|
|
}) as unknown as typeof globalThis.fetch;
|
|
}
|
|
|
|
describe("Discord bot validation and inventory", () => {
|
|
it("measures the final Discord Markdown instead of raw source length", () => {
|
|
expect(discordMarkdownRequiresAttachment("x".repeat(2_000))).toBe(false);
|
|
expect(discordMarkdownRequiresAttachment("🙂".repeat(1_001))).toBe(true);
|
|
// The Discord formatter expands each bare mention by two characters.
|
|
expect(discordMarkdownRequiresAttachment("@a ".repeat(500))).toBe(true);
|
|
expect(
|
|
discordMarkdownRequiresAttachment(
|
|
`\`\`\`ts\n${"const value = 1;\n".repeat(120)}\`\`\``,
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("binds the token, application, privileged intent, and server", async () => {
|
|
const fetch = fetchFixture({
|
|
"/api/v10/users/@me": {
|
|
id: applicationId,
|
|
username: "maya",
|
|
global_name: "Maya",
|
|
bot: true,
|
|
avatar: "avatar-hash",
|
|
},
|
|
"/api/v10/oauth2/applications/@me": {
|
|
id: applicationId,
|
|
name: "Maya",
|
|
flags: 1 << 18,
|
|
},
|
|
[`/api/v10/guilds/${guildId}`]: { id: guildId, name: "Clawd" },
|
|
});
|
|
|
|
await expect(
|
|
verifyDiscordBot({
|
|
applicationId,
|
|
botToken: "discord-secret",
|
|
fetch,
|
|
guildId,
|
|
}),
|
|
).resolves.toEqual({
|
|
providerAccountId: guildId,
|
|
providerAccountLabel: "Clawd",
|
|
botExternalId: applicationId,
|
|
botUsername: "maya",
|
|
botLabel: "Maya",
|
|
botAvatarUrl: `https://cdn.discordapp.com/avatars/${applicationId}/avatar-hash.png`,
|
|
});
|
|
});
|
|
|
|
it("rejects an application mismatch and disabled Message Content intent", async () => {
|
|
const base = {
|
|
"/api/v10/users/@me": {
|
|
id: applicationId,
|
|
username: "maya",
|
|
bot: true,
|
|
},
|
|
[`/api/v10/guilds/${guildId}`]: { id: guildId, name: "Clawd" },
|
|
};
|
|
await expect(
|
|
verifyDiscordBot({
|
|
applicationId,
|
|
botToken: "discord-secret",
|
|
fetch: fetchFixture({
|
|
...base,
|
|
"/api/v10/oauth2/applications/@me": {
|
|
id: "999999999999999999",
|
|
flags: 1 << 18,
|
|
},
|
|
}),
|
|
guildId,
|
|
}),
|
|
).rejects.toThrow("Application ID does not match");
|
|
await expect(
|
|
verifyDiscordBot({
|
|
applicationId,
|
|
botToken: "discord-secret",
|
|
fetch: fetchFixture({
|
|
...base,
|
|
"/api/v10/oauth2/applications/@me": {
|
|
id: applicationId,
|
|
flags: 0,
|
|
},
|
|
}),
|
|
guildId,
|
|
}),
|
|
).rejects.toThrow("Message Content intent is not enabled");
|
|
});
|
|
|
|
it("identifies the failed Discord lookup without exposing provider response text", async () => {
|
|
const rawProviderText = "Invalid Form Body contains private canary";
|
|
const failure = await verifyDiscordBot({
|
|
applicationId,
|
|
botToken: "discord-secret",
|
|
fetch: fetchFixture({
|
|
"/api/v10/users/@me": {
|
|
id: applicationId,
|
|
username: "maya",
|
|
bot: true,
|
|
},
|
|
"/api/v10/oauth2/applications/@me": () =>
|
|
json(
|
|
{
|
|
code: 50035,
|
|
message: rawProviderText,
|
|
errors: {
|
|
application_id: {
|
|
_errors: [
|
|
{ code: "NUMBER_TYPE_COERCE", message: rawProviderText },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
400,
|
|
),
|
|
[`/api/v10/guilds/${guildId}`]: { id: guildId, name: "Clawd" },
|
|
}),
|
|
guildId,
|
|
}).catch((error: unknown) => error);
|
|
|
|
expect(failure).toBeInstanceOf(Error);
|
|
expect((failure as Error).message).toBe(
|
|
"Discord application lookup failed (HTTP 400, code 50035, invalid fields: application_id)",
|
|
);
|
|
expect((failure as Error).message).not.toContain(rawProviderText);
|
|
expect((failure as Error).message).not.toContain("discord-secret");
|
|
});
|
|
|
|
it("discovers only text channels where the bot has the complete safe feature set", async () => {
|
|
const requiredPermissions = "309237763136";
|
|
const fetch = fetchFixture({
|
|
[`/api/v10/guilds/${guildId}`]: { id: guildId, name: "Clawd" },
|
|
[`/api/v10/guilds/${guildId}/members/${applicationId}`]: {
|
|
roles: ["222222222222222222"],
|
|
user: { id: applicationId },
|
|
},
|
|
[`/api/v10/guilds/${guildId}/roles`]: [
|
|
{ id: guildId, permissions: "0" },
|
|
{ id: "222222222222222222", permissions: requiredPermissions },
|
|
],
|
|
[`/api/v10/guilds/${guildId}/channels`]: [
|
|
{
|
|
id: "333333333333333333",
|
|
type: 0,
|
|
name: "agent-lab",
|
|
position: 2,
|
|
permission_overwrites: [],
|
|
},
|
|
{
|
|
id: "444444444444444444",
|
|
type: 0,
|
|
name: "blocked",
|
|
position: 1,
|
|
permission_overwrites: [
|
|
{ id: guildId, type: 0, deny: "2048", allow: "0" },
|
|
],
|
|
},
|
|
{ id: "555555555555555555", type: 2, name: "voice" },
|
|
],
|
|
});
|
|
|
|
await expect(
|
|
listDiscordBotChannels({
|
|
botUserId: applicationId,
|
|
botToken: "discord-secret",
|
|
fetch,
|
|
guildId,
|
|
}),
|
|
).resolves.toEqual({
|
|
provider: "discord",
|
|
resources: [
|
|
{
|
|
providerResourceId: "333333333333333333",
|
|
parentProviderResourceId: guildId,
|
|
type: "channel",
|
|
label: "#agent-lab",
|
|
providerUrl: `https://discord.com/channels/${guildId}/333333333333333333`,
|
|
metadata: { source: "provider_inventory" },
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("fails closed when no channel grants the full required permission set", async () => {
|
|
const fetch = fetchFixture({
|
|
[`/api/v10/guilds/${guildId}`]: { id: guildId, name: "Clawd" },
|
|
[`/api/v10/guilds/${guildId}/members/${applicationId}`]: {
|
|
roles: [],
|
|
user: { id: applicationId },
|
|
},
|
|
[`/api/v10/guilds/${guildId}/roles`]: [
|
|
{ id: guildId, permissions: "1024" },
|
|
],
|
|
[`/api/v10/guilds/${guildId}/channels`]: [
|
|
{ id: "333333333333333333", type: 0, name: "read-only" },
|
|
],
|
|
});
|
|
|
|
await expect(
|
|
listDiscordBotChannels({
|
|
botUserId: applicationId,
|
|
botToken: "discord-secret",
|
|
fetch,
|
|
guildId,
|
|
}),
|
|
).rejects.toThrow("Send Messages in Threads");
|
|
});
|
|
});
|