mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:00:54 +00:00
fix(slack): discover bot scopes via auth test
This commit is contained in:
67
extensions/slack/src/scopes.test.ts
Normal file
67
extensions/slack/src/scopes.test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const createSlackWebClientMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
createSlackWebClient: createSlackWebClientMock,
|
||||
}));
|
||||
|
||||
const { fetchSlackScopes } = await import("./scopes.js");
|
||||
|
||||
function mockSlackClient(apiCall: ReturnType<typeof vi.fn>) {
|
||||
createSlackWebClientMock.mockReturnValue({ apiCall });
|
||||
}
|
||||
|
||||
describe("fetchSlackScopes", () => {
|
||||
beforeEach(() => {
|
||||
createSlackWebClientMock.mockReset();
|
||||
});
|
||||
|
||||
it("uses auth.test response metadata scopes for modern bot tokens", async () => {
|
||||
const apiCall = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
user_id: "U123",
|
||||
response_metadata: { scopes: ["chat:write", "im:history"] },
|
||||
});
|
||||
mockSlackClient(apiCall);
|
||||
|
||||
await expect(fetchSlackScopes("xoxb-token", 1234)).resolves.toEqual({
|
||||
ok: true,
|
||||
scopes: ["chat:write", "im:history"],
|
||||
source: "auth.test",
|
||||
});
|
||||
expect(createSlackWebClientMock).toHaveBeenCalledWith("xoxb-token", { timeout: 1234 });
|
||||
expect(apiCall).toHaveBeenCalledTimes(1);
|
||||
expect(apiCall).toHaveBeenCalledWith("auth.test");
|
||||
});
|
||||
|
||||
it("falls back to legacy scope methods when auth.test has no scope metadata", async () => {
|
||||
const apiCall = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ ok: true })
|
||||
.mockResolvedValueOnce({ ok: true, scopes: "channels:read,chat:write" });
|
||||
mockSlackClient(apiCall);
|
||||
|
||||
await expect(fetchSlackScopes("xoxb-token", 5000)).resolves.toEqual({
|
||||
ok: true,
|
||||
scopes: ["channels:read", "chat:write"],
|
||||
source: "auth.scopes",
|
||||
});
|
||||
expect(apiCall.mock.calls.map((call) => call[0])).toEqual(["auth.test", "auth.scopes"]);
|
||||
});
|
||||
|
||||
it("includes auth.test in the diagnostic when every method fails", async () => {
|
||||
const apiCall = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ ok: false, error: "invalid_auth" })
|
||||
.mockResolvedValueOnce({ ok: false, error: "unknown_method" })
|
||||
.mockResolvedValueOnce({ ok: false, error: "unknown_method" });
|
||||
mockSlackClient(apiCall);
|
||||
|
||||
await expect(fetchSlackScopes("xoxb-token", 5000)).resolves.toEqual({
|
||||
ok: false,
|
||||
error:
|
||||
"auth.test: invalid_auth | auth.scopes: unknown_method | apps.permissions.info: unknown_method",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ export type SlackScopesResult = {
|
||||
};
|
||||
|
||||
type SlackScopesSource = "auth.scopes" | "apps.permissions.info";
|
||||
type SlackScopesMethod = "auth.test" | SlackScopesSource;
|
||||
|
||||
function collectScopes(value: unknown, into: string[]) {
|
||||
if (!value) {
|
||||
@@ -58,6 +59,9 @@ function extractScopes(payload: unknown): string[] {
|
||||
const scopes: string[] = [];
|
||||
collectScopes(payload.scopes, scopes);
|
||||
collectScopes(payload.scope, scopes);
|
||||
if (isRecord(payload.response_metadata)) {
|
||||
collectScopes(payload.response_metadata.scopes, scopes);
|
||||
}
|
||||
if (isRecord(payload.info)) {
|
||||
collectScopes(payload.info.scopes, scopes);
|
||||
collectScopes(payload.info.scope, scopes);
|
||||
@@ -69,7 +73,7 @@ function extractScopes(payload: unknown): string[] {
|
||||
|
||||
async function callSlack(
|
||||
client: WebClient,
|
||||
method: SlackScopesSource,
|
||||
method: SlackScopesMethod,
|
||||
): Promise<Record<string, unknown> | null> {
|
||||
try {
|
||||
const result = await client.apiCall(method);
|
||||
@@ -87,7 +91,7 @@ export async function fetchSlackScopes(
|
||||
timeoutMs: number,
|
||||
): Promise<SlackScopesResult> {
|
||||
const client = createSlackWebClient(token, { timeout: timeoutMs });
|
||||
const attempts: SlackScopesSource[] = ["auth.scopes", "apps.permissions.info"];
|
||||
const attempts: SlackScopesMethod[] = ["auth.test", "auth.scopes", "apps.permissions.info"];
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const method of attempts) {
|
||||
|
||||
Reference in New Issue
Block a user