From a23483959226834293787d41231286ef119968fe Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 9 Oct 2026 00:57:14 -0400 Subject: [PATCH 1/2] test(e2e): match Slack approvals to fixture workspace Pinned OpenClaw stores Slack approvals under team-qualified sender identities. The pairing fixture queried the raw sender and would reject successful approval. Use one explicit fixture team for Socket Mode events and the SQLite expectation. Preserve raw Discord identity checks and the existing live assertions. Validation: two SQLite cases failed before the correction, then all 44 pairing support tests passed. Upstream rebuild reconciliation passed 71 support tests. Live qualification remains pending. Signed-off-by: Julie Yaunches --- test/e2e/live/openclaw-pairing-helpers.ts | 6 ++- .../openclaw-discord-pairing-helpers.test.ts | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/test/e2e/live/openclaw-pairing-helpers.ts b/test/e2e/live/openclaw-pairing-helpers.ts index bffda07aba9..f2b4e077222 100644 --- a/test/e2e/live/openclaw-pairing-helpers.ts +++ b/test/e2e/live/openclaw-pairing-helpers.ts @@ -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: { @@ -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, @@ -617,7 +620,8 @@ export function buildPairingApproveCommand(channel: PairingChannel, code: string } export function buildPairingAllowFromCommand(channel: PairingChannel, user: string): string { - return buildPairingStateCommand("allowed", channel, "", user); + const entry = channel === "slack" ? `team:${SLACK_PAIRING_TEAM}:user:${user}` : user; + return buildPairingStateCommand("allowed", channel, "", entry); } export async function approveAndAssertPairing(options: { diff --git a/test/e2e/support/openclaw-discord-pairing-helpers.test.ts b/test/e2e/support/openclaw-discord-pairing-helpers.test.ts index e49a2e684e1..36fbd3f66a1 100644 --- a/test/e2e/support/openclaw-discord-pairing-helpers.test.ts +++ b/test/e2e/support/openclaw-discord-pairing-helpers.test.ts @@ -197,6 +197,48 @@ 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 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.commit()", + "db.close()", + ].join("\n"), + database, + channel, + storedEntry, + ], + { encoding: "utf8" }, + ); + expect(setup.status, setup.stderr).toBe(0); + const command = buildPairingAllowFromCommand(channel, user).replace( + "/sandbox/.openclaw/state/openclaw.sqlite", + database, + ); + const result = spawnSync("sh", ["-c", command], { encoding: "utf8" }); + expect(result.status, result.stderr).toBe(expectedStatus); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }, + ); + it("loads the managed OpenClaw package without starting a child shell", () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-runtime-managed-root-")); try { From 9b1497f575da9d0f8ef4b27a19dd3e8fc01c7bc7 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 12 Oct 2026 10:51:09 -0400 Subject: [PATCH 2/2] test(e2e): scope Slack pairing requests to their workspace The approval-only correction still failed in live E2E because the probe created a raw user request. Pinned Slack creates workspace-qualified requests and retains the raw sender in metadata. Match that contract before approval, and check the same qualified identity in pending and approved SQLite state. Four focused regressions failed before this correction. All 46 pairing support tests pass. Live Slack qualification remains pending. --- test/e2e/live/openclaw-pairing-helpers.ts | 15 ++-- .../openclaw-discord-pairing-helpers.test.ts | 79 +++++++++++++++++-- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/test/e2e/live/openclaw-pairing-helpers.ts b/test/e2e/live/openclaw-pairing-helpers.ts index f2b4e077222..848c1acc135 100644 --- a/test/e2e/live/openclaw-pairing-helpers.ts +++ b/test/e2e/live/openclaw-pairing-helpers.ts @@ -504,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); }, }); @@ -607,12 +607,16 @@ 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 { @@ -620,8 +624,7 @@ export function buildPairingApproveCommand(channel: PairingChannel, code: string } export function buildPairingAllowFromCommand(channel: PairingChannel, user: string): string { - const entry = channel === "slack" ? `team:${SLACK_PAIRING_TEAM}:user:${user}` : user; - return buildPairingStateCommand("allowed", channel, "", entry); + return buildPairingStateCommand("allowed", channel, "", pairingIdentity(channel, user)); } export async function approveAndAssertPairing(options: { diff --git a/test/e2e/support/openclaw-discord-pairing-helpers.test.ts b/test/e2e/support/openclaw-discord-pairing-helpers.test.ts index 36fbd3f66a1..8dc602bc3e3 100644 --- a/test/e2e/support/openclaw-discord-pairing-helpers.test.ts +++ b/test/e2e/support/openclaw-discord-pairing-helpers.test.ts @@ -203,7 +203,7 @@ describe("OpenClaw Discord pairing helper contracts", () => { ["slack", "U3730E2E", "U3730E2E", 1], ["discord", "1005536447329222676", "1005536447329222676", 0], ] as const)( - "checks the exact %s approval identity %s in SQLite", + "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 { @@ -217,6 +217,8 @@ describe("OpenClaw Discord pairing helper contracts", () => { "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"), @@ -227,18 +229,83 @@ describe("OpenClaw Discord pairing helper contracts", () => { { encoding: "utf8" }, ); expect(setup.status, setup.stderr).toBe(0); - const command = buildPairingAllowFromCommand(channel, user).replace( - "/sandbox/.openclaw/state/openclaw.sqlite", - database, + 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" }, ); - const result = spawnSync("sh", ["-c", command], { encoding: "utf8" }); - expect(result.status, result.stderr).toBe(expectedStatus); + 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 {