refactor: unify extension allowlist resolver and directory scaffolding

This commit is contained in:
Peter Steinberger
2026-03-07 22:13:54 +00:00
parent 8e0e76697a
commit 7230b96cc7
10 changed files with 398 additions and 223 deletions

View File

@@ -0,0 +1,78 @@
import { describe, expect, it, vi } from "vitest";
const {
listTeamsByName,
listChannelsForTeam,
normalizeQuery,
resolveGraphToken,
searchGraphUsers,
} = vi.hoisted(() => ({
listTeamsByName: vi.fn(),
listChannelsForTeam: vi.fn(),
normalizeQuery: vi.fn((value: string) => value.trim().toLowerCase()),
resolveGraphToken: vi.fn(async () => "graph-token"),
searchGraphUsers: vi.fn(),
}));
vi.mock("./graph.js", () => ({
listTeamsByName,
listChannelsForTeam,
normalizeQuery,
resolveGraphToken,
}));
vi.mock("./graph-users.js", () => ({
searchGraphUsers,
}));
import {
resolveMSTeamsChannelAllowlist,
resolveMSTeamsUserAllowlist,
} from "./resolve-allowlist.js";
describe("resolveMSTeamsUserAllowlist", () => {
it("marks empty input unresolved", async () => {
const [result] = await resolveMSTeamsUserAllowlist({ cfg: {}, entries: [" "] });
expect(result).toEqual({ input: " ", resolved: false });
});
it("resolves first Graph user match", async () => {
searchGraphUsers.mockResolvedValueOnce([
{ id: "user-1", displayName: "Alice One" },
{ id: "user-2", displayName: "Alice Two" },
]);
const [result] = await resolveMSTeamsUserAllowlist({ cfg: {}, entries: ["alice"] });
expect(result).toEqual({
input: "alice",
resolved: true,
id: "user-1",
name: "Alice One",
note: "multiple matches; chose first",
});
});
});
describe("resolveMSTeamsChannelAllowlist", () => {
it("resolves team/channel by team name + channel display name", async () => {
listTeamsByName.mockResolvedValueOnce([{ id: "team-1", displayName: "Product Team" }]);
listChannelsForTeam.mockResolvedValueOnce([
{ id: "channel-1", displayName: "General" },
{ id: "channel-2", displayName: "Roadmap" },
]);
const [result] = await resolveMSTeamsChannelAllowlist({
cfg: {},
entries: ["Product Team/Roadmap"],
});
expect(result).toEqual({
input: "Product Team/Roadmap",
resolved: true,
teamId: "team-1",
teamName: "Product Team",
channelId: "channel-2",
channelName: "Roadmap",
note: "multiple channels; chose first",
});
});
});

View File

@@ -1,3 +1,4 @@
import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk";
import { searchGraphUsers } from "./graph-users.js";
import {
listChannelsForTeam,
@@ -105,61 +106,55 @@ export async function resolveMSTeamsChannelAllowlist(params: {
entries: string[];
}): Promise<MSTeamsChannelResolution[]> {
const token = await resolveGraphToken(params.cfg);
const results: MSTeamsChannelResolution[] = [];
for (const input of params.entries) {
const { team, channel } = parseMSTeamsTeamChannelInput(input);
if (!team) {
results.push({ input, resolved: false });
continue;
}
const teams = /^[0-9a-fA-F-]{16,}$/.test(team)
? [{ id: team, displayName: team }]
: await listTeamsByName(token, team);
if (teams.length === 0) {
results.push({ input, resolved: false, note: "team not found" });
continue;
}
const teamMatch = teams[0];
const teamId = teamMatch.id?.trim();
const teamName = teamMatch.displayName?.trim() || team;
if (!teamId) {
results.push({ input, resolved: false, note: "team id missing" });
continue;
}
if (!channel) {
results.push({
return await mapAllowlistResolutionInputs({
inputs: params.entries,
mapInput: async (input): Promise<MSTeamsChannelResolution> => {
const { team, channel } = parseMSTeamsTeamChannelInput(input);
if (!team) {
return { input, resolved: false };
}
const teams = /^[0-9a-fA-F-]{16,}$/.test(team)
? [{ id: team, displayName: team }]
: await listTeamsByName(token, team);
if (teams.length === 0) {
return { input, resolved: false, note: "team not found" };
}
const teamMatch = teams[0];
const teamId = teamMatch.id?.trim();
const teamName = teamMatch.displayName?.trim() || team;
if (!teamId) {
return { input, resolved: false, note: "team id missing" };
}
if (!channel) {
return {
input,
resolved: true,
teamId,
teamName,
note: teams.length > 1 ? "multiple teams; chose first" : undefined,
};
}
const channels = await listChannelsForTeam(token, teamId);
const channelMatch =
channels.find((item) => item.id === channel) ??
channels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ??
channels.find((item) =>
item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""),
);
if (!channelMatch?.id) {
return { input, resolved: false, note: "channel not found" };
}
return {
input,
resolved: true,
teamId,
teamName,
note: teams.length > 1 ? "multiple teams; chose first" : undefined,
});
continue;
}
const channels = await listChannelsForTeam(token, teamId);
const channelMatch =
channels.find((item) => item.id === channel) ??
channels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ??
channels.find((item) =>
item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""),
);
if (!channelMatch?.id) {
results.push({ input, resolved: false, note: "channel not found" });
continue;
}
results.push({
input,
resolved: true,
teamId,
teamName,
channelId: channelMatch.id,
channelName: channelMatch.displayName ?? channel,
note: channels.length > 1 ? "multiple channels; chose first" : undefined,
});
}
return results;
channelId: channelMatch.id,
channelName: channelMatch.displayName ?? channel,
note: channels.length > 1 ? "multiple channels; chose first" : undefined,
};
},
});
}
export async function resolveMSTeamsUserAllowlist(params: {
@@ -167,32 +162,28 @@ export async function resolveMSTeamsUserAllowlist(params: {
entries: string[];
}): Promise<MSTeamsUserResolution[]> {
const token = await resolveGraphToken(params.cfg);
const results: MSTeamsUserResolution[] = [];
for (const input of params.entries) {
const query = normalizeQuery(normalizeMSTeamsUserInput(input));
if (!query) {
results.push({ input, resolved: false });
continue;
}
if (/^[0-9a-fA-F-]{16,}$/.test(query)) {
results.push({ input, resolved: true, id: query });
continue;
}
const users = await searchGraphUsers({ token, query, top: 10 });
const match = users[0];
if (!match?.id) {
results.push({ input, resolved: false });
continue;
}
results.push({
input,
resolved: true,
id: match.id,
name: match.displayName ?? undefined,
note: users.length > 1 ? "multiple matches; chose first" : undefined,
});
}
return results;
return await mapAllowlistResolutionInputs({
inputs: params.entries,
mapInput: async (input): Promise<MSTeamsUserResolution> => {
const query = normalizeQuery(normalizeMSTeamsUserInput(input));
if (!query) {
return { input, resolved: false };
}
if (/^[0-9a-fA-F-]{16,}$/.test(query)) {
return { input, resolved: true, id: query };
}
const users = await searchGraphUsers({ token, query, top: 10 });
const match = users[0];
if (!match?.id) {
return { input, resolved: false };
}
return {
input,
resolved: true,
id: match.id,
name: match.displayName ?? undefined,
note: users.length > 1 ? "multiple matches; chose first" : undefined,
};
},
});
}