Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions test/e2e/live/openclaw-pairing-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const PAIRING_USER = {
discord: process.env.NEMOCLAW_DISCORD_PAIRING_USER ?? "1005536447329222676",
};

const SLACK_PAIRING_TEAM = "T3730E2E";

export const DISCORD_DM_CHANNEL = process.env.NEMOCLAW_DISCORD_DM_CHANNEL ?? "1199988877766655554";

export function pairingEnv(options: {
Expand Down Expand Up @@ -176,6 +178,7 @@ export async function startFakeSlackApi(
FAKE_SLACK_API_EXPECTED_BOT_TOKEN: botToken,
FAKE_SLACK_API_EXPECTED_APP_TOKEN: appToken,
FAKE_SLACK_API_SOCKET_USER_ID: PAIRING_USER.slack,
FAKE_SLACK_API_SOCKET_TEAM_ID: SLACK_PAIRING_TEAM,
},
env,
redactionValues: redactions,
Expand Down Expand Up @@ -501,13 +504,13 @@ function postPairingReply(text, channel) {
const { issuePairingChallenge, upsertChannelPairingRequest } = await loadConversationRuntime();
const envelope = await receiveSlackSocketEvent();
const event = envelope?.payload?.event;
if (!event || event.type !== "message" || !event.user || !event.channel) throw new Error("unexpected fake Slack envelope: " + JSON.stringify(envelope).slice(0, 400));
if (!event || event.type !== "message" || !event.user || !event.channel || !envelope.payload?.team_id) throw new Error("unexpected fake Slack envelope: " + JSON.stringify(envelope).slice(0, 400));
if (event.user !== process.env.SLACK_PAIRING_USER) throw new Error("unexpected fake Slack user: " + event.user);
const result = await issuePairingChallenge({
channel: "slack",
senderId: event.user,
senderId: "team:" + encodeURIComponent(envelope.payload.team_id) + ":user:" + encodeURIComponent(event.user),
senderIdLine: "Slack user ID: " + event.user,
meta: { accountId: "default", channelId: event.channel, teamId: envelope.payload?.team_id || "" },
meta: { accountId: "default", channelId: event.channel, teamId: envelope.payload.team_id, senderId: event.user },
upsertPairingRequest: async ({ id, meta }) => upsertChannelPairingRequest({ channel: "slack", id, accountId: "default", meta }),
sendPairingReply: async (text) => { await postPairingReply(text, event.channel); },
});
Expand Down Expand Up @@ -604,20 +607,24 @@ function buildPairingStateCommand(
return `python3 -c ${shellQuote(PAIRING_STATE_PROBE_SOURCE)} ${shellQuote(query)} ${shellQuote(JSON.stringify(parameters))}`;
}

function pairingIdentity(channel: PairingChannel, user: string): string {
return channel === "slack" ? `team:${SLACK_PAIRING_TEAM}:user:${encodeURIComponent(user)}` : user;
}

export function buildPairingPendingCommand(
channel: PairingChannel,
code: string,
user: string,
): string {
return buildPairingStateCommand("pending", channel, code, user);
return buildPairingStateCommand("pending", channel, code, pairingIdentity(channel, user));
}

export function buildPairingApproveCommand(channel: PairingChannel, code: string): string {
return `openclaw pairing approve ${channel} ${shellQuote(code)} 2>&1`;
}

export function buildPairingAllowFromCommand(channel: PairingChannel, user: string): string {
return buildPairingStateCommand("allowed", channel, "", user);
return buildPairingStateCommand("allowed", channel, "", pairingIdentity(channel, user));
}

export async function approveAndAssertPairing(options: {
Expand Down
109 changes: 109 additions & 0 deletions test/e2e/support/openclaw-discord-pairing-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,115 @@ describe("OpenClaw Discord pairing helper contracts", () => {
expect(approveCommand).not.toContain('"abc$(touch /tmp/e2e-should-not-run)"');
});

it.each([
["slack", "team:T3730E2E:user:U3730E2E", "U3730E2E", 0],
["slack", "team:TOTHER:user:U3730E2E", "U3730E2E", 1],
["slack", "U3730E2E", "U3730E2E", 1],
["discord", "1005536447329222676", "1005536447329222676", 0],
] as const)(
"checks the exact %s pending and approval identity %s in SQLite",
(channel, storedEntry, user, expectedStatus) => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pairing-approval-state-"));
try {
const database = path.join(tmp, "openclaw.sqlite");
const setup = spawnSync(
"python3",
[
"-c",
[
"import sqlite3, sys",
"db = sqlite3.connect(sys.argv[1])",
"db.execute('CREATE TABLE channel_pairing_allow_entries (channel_key TEXT, entry TEXT)')",
"db.execute('INSERT INTO channel_pairing_allow_entries VALUES (?, ?)', sys.argv[2:])",
"db.execute('CREATE TABLE channel_pairing_requests (channel_key TEXT, code TEXT, request_id TEXT)')",
"db.execute('INSERT INTO channel_pairing_requests VALUES (?, ?, ?)', (sys.argv[2], 'CODE1234', sys.argv[3]))",
"db.commit()",
"db.close()",
].join("\n"),
database,
channel,
storedEntry,
],
{ encoding: "utf8" },
);
expect(setup.status, setup.stderr).toBe(0);
const pending = spawnSync(
"sh",
[
"-c",
buildPairingPendingCommand(channel, "CODE1234", user).replace(
"/sandbox/.openclaw/state/openclaw.sqlite",
database,
),
],
{ encoding: "utf8" },
);
expect(pending.status, pending.stderr).toBe(expectedStatus);
const approved = spawnSync(
"sh",
[
"-c",
buildPairingAllowFromCommand(channel, user).replace(
"/sandbox/.openclaw/state/openclaw.sqlite",
database,
),
],
{ encoding: "utf8" },
);
expect(approved.status, approved.stderr).toBe(expectedStatus);
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
},
);

it.each(["T3730E2E", "TOTHER"])(
"issues a Slack request scoped to the event workspace %s",
(teamId) => {
const start = SLACK_PAIRING_SCRIPT.indexOf(
"const { issuePairingChallenge, upsertChannelPairingRequest } = await loadConversationRuntime();",
);
const probe = SLACK_PAIRING_SCRIPT.slice(start, SLACK_PAIRING_SCRIPT.lastIndexOf("\nNODE"));
const result = spawnSync(process.execPath, ["--input-type=module"], {
input: `
async function loadConversationRuntime() {
return {
issuePairingChallenge: async (params) => {
const result = await params.upsertPairingRequest({ id: params.senderId, meta: params.meta });
await params.sendPairingReply(params.senderIdLine);
return result;
},
upsertChannelPairingRequest: async (params) => {
console.log("REQUEST " + JSON.stringify(params));
return { code: "CODE1234", created: true };
},
};
}
async function receiveSlackSocketEvent() {
return ${JSON.stringify({ payload: { team_id: teamId, event: { type: "message", user: "U3730E2E", channel: "D3730E2E" } } })};
}
async function postPairingReply() {}
${probe}
`,
encoding: "utf8",
env: { ...process.env, SLACK_PAIRING_USER: "U3730E2E" },
});
expect(result.status, result.stderr).toBe(0);
const request = result.stdout.split("\n").find((line) => line.startsWith("REQUEST "));
expect(JSON.parse(request?.slice("REQUEST ".length) ?? "null")).toEqual({
channel: "slack",
id: `team:${teamId}:user:U3730E2E`,
accountId: "default",
meta: {
accountId: "default",
channelId: "D3730E2E",
teamId,
senderId: "U3730E2E",
},
});
},
);

it("loads the managed OpenClaw package without starting a child shell", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-runtime-managed-root-"));
try {
Expand Down
Loading