mirror of https://github.com/openclaw/openclaw.git
refactor: share mattermost test harnesses
This commit is contained in:
parent
48853f875b
commit
ba2d57d024
|
|
@ -16,6 +16,35 @@ const accountFixture: ResolvedMattermostAccount = {
|
||||||
config: {},
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function authorizeGroupCommand(senderId: string) {
|
||||||
|
return authorizeMattermostCommandInvocation({
|
||||||
|
account: {
|
||||||
|
...accountFixture,
|
||||||
|
config: {
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
allowFrom: ["trusted-user"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cfg: {
|
||||||
|
commands: {
|
||||||
|
useAccessGroups: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
senderId,
|
||||||
|
senderName: senderId,
|
||||||
|
channelId: "chan-1",
|
||||||
|
channelInfo: {
|
||||||
|
id: "chan-1",
|
||||||
|
type: "O",
|
||||||
|
name: "general",
|
||||||
|
display_name: "General",
|
||||||
|
},
|
||||||
|
storeAllowFrom: [],
|
||||||
|
allowTextCommands: true,
|
||||||
|
hasControlCommand: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe("mattermost monitor authz", () => {
|
describe("mattermost monitor authz", () => {
|
||||||
it("keeps DM allowlist merged with pairing-store entries", () => {
|
it("keeps DM allowlist merged with pairing-store entries", () => {
|
||||||
const resolved = resolveMattermostEffectiveAllowFromLists({
|
const resolved = resolveMattermostEffectiveAllowFromLists({
|
||||||
|
|
@ -72,32 +101,7 @@ describe("mattermost monitor authz", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("denies group control commands when the sender is outside the allowlist", () => {
|
it("denies group control commands when the sender is outside the allowlist", () => {
|
||||||
const decision = authorizeMattermostCommandInvocation({
|
const decision = authorizeGroupCommand("attacker");
|
||||||
account: {
|
|
||||||
...accountFixture,
|
|
||||||
config: {
|
|
||||||
groupPolicy: "allowlist",
|
|
||||||
allowFrom: ["trusted-user"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cfg: {
|
|
||||||
commands: {
|
|
||||||
useAccessGroups: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
senderId: "attacker",
|
|
||||||
senderName: "attacker",
|
|
||||||
channelId: "chan-1",
|
|
||||||
channelInfo: {
|
|
||||||
id: "chan-1",
|
|
||||||
type: "O",
|
|
||||||
name: "general",
|
|
||||||
display_name: "General",
|
|
||||||
},
|
|
||||||
storeAllowFrom: [],
|
|
||||||
allowTextCommands: true,
|
|
||||||
hasControlCommand: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(decision).toMatchObject({
|
expect(decision).toMatchObject({
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|
@ -107,32 +111,7 @@ describe("mattermost monitor authz", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("authorizes group control commands for allowlisted senders", () => {
|
it("authorizes group control commands for allowlisted senders", () => {
|
||||||
const decision = authorizeMattermostCommandInvocation({
|
const decision = authorizeGroupCommand("trusted-user");
|
||||||
account: {
|
|
||||||
...accountFixture,
|
|
||||||
config: {
|
|
||||||
groupPolicy: "allowlist",
|
|
||||||
allowFrom: ["trusted-user"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cfg: {
|
|
||||||
commands: {
|
|
||||||
useAccessGroups: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
senderId: "trusted-user",
|
|
||||||
senderName: "trusted-user",
|
|
||||||
channelId: "chan-1",
|
|
||||||
channelInfo: {
|
|
||||||
id: "chan-1",
|
|
||||||
type: "O",
|
|
||||||
name: "general",
|
|
||||||
display_name: "General",
|
|
||||||
},
|
|
||||||
storeAllowFrom: [],
|
|
||||||
allowTextCommands: true,
|
|
||||||
hasControlCommand: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(decision).toMatchObject({
|
expect(decision).toMatchObject({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,28 @@ describe("mattermost reactions", () => {
|
||||||
resetMattermostReactionBotUserCacheForTests();
|
resetMattermostReactionBotUserCacheForTests();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function addReactionWithFetch(
|
||||||
|
fetchMock: ReturnType<typeof createMattermostReactionFetchMock>,
|
||||||
|
) {
|
||||||
|
return addMattermostReaction({
|
||||||
|
cfg: createMattermostTestConfig(),
|
||||||
|
postId: "POST1",
|
||||||
|
emojiName: "thumbsup",
|
||||||
|
fetchImpl: fetchMock as unknown as typeof fetch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeReactionWithFetch(
|
||||||
|
fetchMock: ReturnType<typeof createMattermostReactionFetchMock>,
|
||||||
|
) {
|
||||||
|
return removeMattermostReaction({
|
||||||
|
cfg: createMattermostTestConfig(),
|
||||||
|
postId: "POST1",
|
||||||
|
emojiName: "thumbsup",
|
||||||
|
fetchImpl: fetchMock as unknown as typeof fetch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it("adds reactions by calling /users/me then POST /reactions", async () => {
|
it("adds reactions by calling /users/me then POST /reactions", async () => {
|
||||||
const fetchMock = createMattermostReactionFetchMock({
|
const fetchMock = createMattermostReactionFetchMock({
|
||||||
mode: "add",
|
mode: "add",
|
||||||
|
|
@ -21,12 +43,7 @@ describe("mattermost reactions", () => {
|
||||||
emojiName: "thumbsup",
|
emojiName: "thumbsup",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await addMattermostReaction({
|
const result = await addReactionWithFetch(fetchMock);
|
||||||
cfg: createMattermostTestConfig(),
|
|
||||||
postId: "POST1",
|
|
||||||
emojiName: "thumbsup",
|
|
||||||
fetchImpl: fetchMock as unknown as typeof fetch,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toEqual({ ok: true });
|
expect(result).toEqual({ ok: true });
|
||||||
expect(fetchMock).toHaveBeenCalled();
|
expect(fetchMock).toHaveBeenCalled();
|
||||||
|
|
@ -41,12 +58,7 @@ describe("mattermost reactions", () => {
|
||||||
body: { id: "err", message: "boom" },
|
body: { id: "err", message: "boom" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await addMattermostReaction({
|
const result = await addReactionWithFetch(fetchMock);
|
||||||
cfg: createMattermostTestConfig(),
|
|
||||||
postId: "POST1",
|
|
||||||
emojiName: "thumbsup",
|
|
||||||
fetchImpl: fetchMock as unknown as typeof fetch,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.ok).toBe(false);
|
expect(result.ok).toBe(false);
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
|
|
@ -61,12 +73,7 @@ describe("mattermost reactions", () => {
|
||||||
emojiName: "thumbsup",
|
emojiName: "thumbsup",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await removeMattermostReaction({
|
const result = await removeReactionWithFetch(fetchMock);
|
||||||
cfg: createMattermostTestConfig(),
|
|
||||||
postId: "POST1",
|
|
||||||
emojiName: "thumbsup",
|
|
||||||
fetchImpl: fetchMock as unknown as typeof fetch,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toEqual({ ok: true });
|
expect(result).toEqual({ ok: true });
|
||||||
expect(fetchMock).toHaveBeenCalled();
|
expect(fetchMock).toHaveBeenCalled();
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,25 @@ import {
|
||||||
} from "./slash-commands.js";
|
} from "./slash-commands.js";
|
||||||
|
|
||||||
describe("slash-commands", () => {
|
describe("slash-commands", () => {
|
||||||
|
async function registerSingleStatusCommand(
|
||||||
|
request: (path: string, init?: { method?: string }) => Promise<unknown>,
|
||||||
|
) {
|
||||||
|
const client = { request } as unknown as MattermostClient;
|
||||||
|
return registerSlashCommands({
|
||||||
|
client,
|
||||||
|
teamId: "team-1",
|
||||||
|
creatorUserId: "bot-user",
|
||||||
|
callbackUrl: "http://gateway/callback",
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
trigger: "oc_status",
|
||||||
|
description: "status",
|
||||||
|
autoComplete: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it("parses application/x-www-form-urlencoded payloads", () => {
|
it("parses application/x-www-form-urlencoded payloads", () => {
|
||||||
const payload = parseSlashCommandPayload(
|
const payload = parseSlashCommandPayload(
|
||||||
"token=t1&team_id=team&channel_id=ch1&user_id=u1&command=%2Foc_status&text=now",
|
"token=t1&team_id=team&channel_id=ch1&user_id=u1&command=%2Foc_status&text=now",
|
||||||
|
|
@ -101,21 +120,7 @@ describe("slash-commands", () => {
|
||||||
}
|
}
|
||||||
throw new Error(`unexpected request path: ${path}`);
|
throw new Error(`unexpected request path: ${path}`);
|
||||||
});
|
});
|
||||||
const client = { request } as unknown as MattermostClient;
|
const result = await registerSingleStatusCommand(request);
|
||||||
|
|
||||||
const result = await registerSlashCommands({
|
|
||||||
client,
|
|
||||||
teamId: "team-1",
|
|
||||||
creatorUserId: "bot-user",
|
|
||||||
callbackUrl: "http://gateway/callback",
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
trigger: "oc_status",
|
|
||||||
description: "status",
|
|
||||||
autoComplete: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0]?.managed).toBe(false);
|
expect(result[0]?.managed).toBe(false);
|
||||||
|
|
@ -144,21 +149,7 @@ describe("slash-commands", () => {
|
||||||
}
|
}
|
||||||
throw new Error(`unexpected request path: ${path}`);
|
throw new Error(`unexpected request path: ${path}`);
|
||||||
});
|
});
|
||||||
const client = { request } as unknown as MattermostClient;
|
const result = await registerSingleStatusCommand(request);
|
||||||
|
|
||||||
const result = await registerSlashCommands({
|
|
||||||
client,
|
|
||||||
teamId: "team-1",
|
|
||||||
creatorUserId: "bot-user",
|
|
||||||
callbackUrl: "http://gateway/callback",
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
trigger: "oc_status",
|
|
||||||
description: "status",
|
|
||||||
autoComplete: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toHaveLength(0);
|
expect(result).toHaveLength(0);
|
||||||
expect(request).toHaveBeenCalledTimes(1);
|
expect(request).toHaveBeenCalledTimes(1);
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,23 @@ const accountFixture: ResolvedMattermostAccount = {
|
||||||
config: {},
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function runSlashRequest(params: {
|
||||||
|
commandTokens: Set<string>;
|
||||||
|
body: string;
|
||||||
|
method?: string;
|
||||||
|
}) {
|
||||||
|
const handler = createSlashCommandHttpHandler({
|
||||||
|
account: accountFixture,
|
||||||
|
cfg: {} as OpenClawConfig,
|
||||||
|
runtime: {} as RuntimeEnv,
|
||||||
|
commandTokens: params.commandTokens,
|
||||||
|
});
|
||||||
|
const req = createRequest({ method: params.method, body: params.body });
|
||||||
|
const response = createResponse();
|
||||||
|
await handler(req, response.res);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
describe("slash-http", () => {
|
describe("slash-http", () => {
|
||||||
it("rejects non-POST methods", async () => {
|
it("rejects non-POST methods", async () => {
|
||||||
const handler = createSlashCommandHttpHandler({
|
const handler = createSlashCommandHttpHandler({
|
||||||
|
|
@ -93,36 +110,20 @@ describe("slash-http", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fails closed when no command tokens are registered", async () => {
|
it("fails closed when no command tokens are registered", async () => {
|
||||||
const handler = createSlashCommandHttpHandler({
|
const response = await runSlashRequest({
|
||||||
account: accountFixture,
|
|
||||||
cfg: {} as OpenClawConfig,
|
|
||||||
runtime: {} as RuntimeEnv,
|
|
||||||
commandTokens: new Set<string>(),
|
commandTokens: new Set<string>(),
|
||||||
});
|
|
||||||
const req = createRequest({
|
|
||||||
body: "token=tok1&team_id=t1&channel_id=c1&user_id=u1&command=%2Foc_status&text=",
|
body: "token=tok1&team_id=t1&channel_id=c1&user_id=u1&command=%2Foc_status&text=",
|
||||||
});
|
});
|
||||||
const response = createResponse();
|
|
||||||
|
|
||||||
await handler(req, response.res);
|
|
||||||
|
|
||||||
expect(response.res.statusCode).toBe(401);
|
expect(response.res.statusCode).toBe(401);
|
||||||
expect(response.getBody()).toContain("Unauthorized: invalid command token.");
|
expect(response.getBody()).toContain("Unauthorized: invalid command token.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects unknown command tokens", async () => {
|
it("rejects unknown command tokens", async () => {
|
||||||
const handler = createSlashCommandHttpHandler({
|
const response = await runSlashRequest({
|
||||||
account: accountFixture,
|
|
||||||
cfg: {} as OpenClawConfig,
|
|
||||||
runtime: {} as RuntimeEnv,
|
|
||||||
commandTokens: new Set(["known-token"]),
|
commandTokens: new Set(["known-token"]),
|
||||||
});
|
|
||||||
const req = createRequest({
|
|
||||||
body: "token=unknown&team_id=t1&channel_id=c1&user_id=u1&command=%2Foc_status&text=",
|
body: "token=unknown&team_id=t1&channel_id=c1&user_id=u1&command=%2Foc_status&text=",
|
||||||
});
|
});
|
||||||
const response = createResponse();
|
|
||||||
|
|
||||||
await handler(req, response.res);
|
|
||||||
|
|
||||||
expect(response.res.statusCode).toBe(401);
|
expect(response.res.statusCode).toBe(401);
|
||||||
expect(response.getBody()).toContain("Unauthorized: invalid command token.");
|
expect(response.getBody()).toContain("Unauthorized: invalid command token.");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue