refactor: share plugin directory helpers

This commit is contained in:
Peter Steinberger
2026-03-14 00:27:18 +00:00
parent 5eaa14687f
commit 614844c9fe
4 changed files with 68 additions and 50 deletions

View File

@@ -6,6 +6,13 @@ import {
listDirectoryUserEntriesFromAllowFrom,
} from "./directory-config-helpers.js";
function expectUserDirectoryEntries(entries: unknown) {
expect(entries).toEqual([
{ kind: "user", id: "alice" },
{ kind: "user", id: "carla" },
]);
}
describe("listDirectoryUserEntriesFromAllowFrom", () => {
it("normalizes, deduplicates, filters, and limits user ids", () => {
const entries = listDirectoryUserEntriesFromAllowFrom({
@@ -15,10 +22,7 @@ describe("listDirectoryUserEntriesFromAllowFrom", () => {
limit: 2,
});
expect(entries).toEqual([
{ kind: "user", id: "alice" },
{ kind: "user", id: "carla" },
]);
expectUserDirectoryEntries(entries);
});
});
@@ -54,10 +58,7 @@ describe("listDirectoryUserEntriesFromAllowFromAndMapKeys", () => {
limit: 2,
});
expect(entries).toEqual([
{ kind: "user", id: "alice" },
{ kind: "user", id: "carla" },
]);
expectUserDirectoryEntries(entries);
});
});

View File

@@ -22,12 +22,12 @@ export function toDirectoryEntries(kind: "user" | "group", ids: string[]): Chann
return ids.map((id) => ({ kind, id }) as const);
}
function collectDirectoryIdsFromEntries(params: {
entries?: readonly unknown[];
function normalizeDirectoryIds(params: {
rawIds: readonly string[];
normalizeId?: (entry: string) => string | null | undefined;
}): string[] {
return (params.entries ?? [])
.map((entry) => String(entry).trim())
return params.rawIds
.map((entry) => entry.trim())
.filter((entry) => Boolean(entry) && entry !== "*")
.map((entry) => {
const normalized = params.normalizeId ? params.normalizeId(entry) : entry;
@@ -36,18 +36,24 @@ function collectDirectoryIdsFromEntries(params: {
.filter(Boolean);
}
function collectDirectoryIdsFromEntries(params: {
entries?: readonly unknown[];
normalizeId?: (entry: string) => string | null | undefined;
}): string[] {
return normalizeDirectoryIds({
rawIds: (params.entries ?? []).map((entry) => String(entry)),
normalizeId: params.normalizeId,
});
}
function collectDirectoryIdsFromMapKeys(params: {
groups?: Record<string, unknown>;
normalizeId?: (entry: string) => string | null | undefined;
}): string[] {
return Object.keys(params.groups ?? {})
.map((entry) => entry.trim())
.filter((entry) => Boolean(entry) && entry !== "*")
.map((entry) => {
const normalized = params.normalizeId ? params.normalizeId(entry) : entry;
return typeof normalized === "string" ? normalized.trim() : "";
})
.filter(Boolean);
return normalizeDirectoryIds({
rawIds: Object.keys(params.groups ?? {}),
normalizeId: params.normalizeId,
});
}
function dedupeDirectoryIds(ids: string[]): string[] {

View File

@@ -410,33 +410,43 @@ describe("resolveChannelConfigWrites", () => {
});
describe("authorizeConfigWrite", () => {
it("blocks when a target account disables writes", () => {
const cfg = makeSlackConfigWritesCfg("work");
function expectConfigWriteBlocked(params: {
disabledAccountId: string;
reason: "target-disabled" | "origin-disabled";
blockedScope: "target" | "origin";
}) {
expect(
authorizeConfigWrite({
cfg,
cfg: makeSlackConfigWritesCfg(params.disabledAccountId),
origin: { channelId: "slack", accountId: "default" },
target: resolveExplicitConfigWriteTarget({ channelId: "slack", accountId: "work" }),
}),
).toEqual({
allowed: false,
reason: params.reason,
blockedScope: {
kind: params.blockedScope,
scope: {
channelId: "slack",
accountId: params.blockedScope === "target" ? "work" : "default",
},
},
});
}
it("blocks when a target account disables writes", () => {
expectConfigWriteBlocked({
disabledAccountId: "work",
reason: "target-disabled",
blockedScope: { kind: "target", scope: { channelId: "slack", accountId: "work" } },
blockedScope: "target",
});
});
it("blocks when the origin account disables writes", () => {
const cfg = makeSlackConfigWritesCfg("default");
expect(
authorizeConfigWrite({
cfg,
origin: { channelId: "slack", accountId: "default" },
target: resolveExplicitConfigWriteTarget({ channelId: "slack", accountId: "work" }),
}),
).toEqual({
allowed: false,
expectConfigWriteBlocked({
disabledAccountId: "default",
reason: "origin-disabled",
blockedScope: { kind: "origin", scope: { channelId: "slack", accountId: "default" } },
blockedScope: "origin",
});
});

View File

@@ -41,6 +41,19 @@ async function buildSnapshotFromAccount<ResolvedAccount>(params: {
};
}
function inspectChannelAccount<ResolvedAccount>(params: {
plugin: ChannelPlugin<ResolvedAccount>;
cfg: OpenClawConfig;
accountId: string;
}): ResolvedAccount | null {
return (params.plugin.config.inspectAccount?.(params.cfg, params.accountId) ??
inspectReadOnlyChannelAccount({
channelId: params.plugin.id,
cfg: params.cfg,
accountId: params.accountId,
})) as ResolvedAccount | null;
}
export async function buildReadOnlySourceChannelAccountSnapshot<ResolvedAccount>(params: {
plugin: ChannelPlugin<ResolvedAccount>;
cfg: OpenClawConfig;
@@ -49,13 +62,7 @@ export async function buildReadOnlySourceChannelAccountSnapshot<ResolvedAccount>
probe?: unknown;
audit?: unknown;
}): Promise<ChannelAccountSnapshot | null> {
const inspectedAccount =
params.plugin.config.inspectAccount?.(params.cfg, params.accountId) ??
inspectReadOnlyChannelAccount({
channelId: params.plugin.id,
cfg: params.cfg,
accountId: params.accountId,
});
const inspectedAccount = inspectChannelAccount(params);
if (!inspectedAccount) {
return null;
}
@@ -73,15 +80,9 @@ export async function buildChannelAccountSnapshot<ResolvedAccount>(params: {
probe?: unknown;
audit?: unknown;
}): Promise<ChannelAccountSnapshot> {
const inspectedAccount =
params.plugin.config.inspectAccount?.(params.cfg, params.accountId) ??
inspectReadOnlyChannelAccount({
channelId: params.plugin.id,
cfg: params.cfg,
accountId: params.accountId,
});
const account = (inspectedAccount ??
params.plugin.config.resolveAccount(params.cfg, params.accountId)) as ResolvedAccount;
const inspectedAccount = inspectChannelAccount(params);
const account =
inspectedAccount ?? params.plugin.config.resolveAccount(params.cfg, params.accountId);
return await buildSnapshotFromAccount({
...params,
account,