test: speed up msteams setup surface

This commit is contained in:
Peter Steinberger
2026-04-07 13:36:33 +01:00
parent 9d26b1056f
commit b747e0c34d
3 changed files with 154 additions and 139 deletions

View File

@@ -1,4 +1,14 @@
import { DEFAULT_ACCOUNT_ID, type ChannelSetupAdapter } from "openclaw/plugin-sdk/setup";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import {
createStandardChannelSetupStatus,
DEFAULT_ACCOUNT_ID,
type ChannelSetupAdapter,
type ChannelSetupWizard,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup";
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import { normalizeSecretInputString } from "./secret-input.js";
import { hasConfiguredMSTeamsCredentials, resolveMSTeamsCredentials } from "./token.js";
export const msteamsSetupAdapter: ChannelSetupAdapter = {
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
@@ -13,3 +23,138 @@ export const msteamsSetupAdapter: ChannelSetupAdapter = {
},
}),
};
const channel = "msteams" as const;
async function promptMSTeamsCredentials(prompter: WizardPrompter): Promise<{
appId: string;
appPassword: string;
tenantId: string;
}> {
const appId = String(
await prompter.text({
message: "Enter MS Teams App ID",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
const appPassword = String(
await prompter.text({
message: "Enter MS Teams App Password",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
const tenantId = String(
await prompter.text({
message: "Enter MS Teams Tenant ID",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
return { appId, appPassword, tenantId };
}
async function noteMSTeamsCredentialHelp(prompter: WizardPrompter): Promise<void> {
await prompter.note(
[
"1) Azure Bot registration -> get App ID + Tenant ID",
"2) Add a client secret (App Password)",
"3) Set webhook URL + messaging endpoint",
"Tip: you can also set MSTEAMS_APP_ID / MSTEAMS_APP_PASSWORD / MSTEAMS_TENANT_ID.",
`Docs: ${formatDocsLink("/channels/msteams", "msteams")}`,
].join("\n"),
"MS Teams credentials",
);
}
export function createMSTeamsSetupWizardBase(): Pick<
ChannelSetupWizard,
| "channel"
| "resolveAccountIdForConfigure"
| "resolveShouldPromptAccountIds"
| "status"
| "credentials"
| "finalize"
> {
return {
channel,
resolveAccountIdForConfigure: () => DEFAULT_ACCOUNT_ID,
resolveShouldPromptAccountIds: () => false,
status: createStandardChannelSetupStatus({
channelLabel: "MS Teams",
configuredLabel: "configured",
unconfiguredLabel: "needs app credentials",
configuredHint: "configured",
unconfiguredHint: "needs app creds",
configuredScore: 2,
unconfiguredScore: 0,
includeStatusLine: true,
resolveConfigured: ({ cfg }) =>
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)) ||
hasConfiguredMSTeamsCredentials(cfg.channels?.msteams),
}),
credentials: [],
finalize: async ({ cfg, prompter }) => {
const resolved = resolveMSTeamsCredentials(cfg.channels?.msteams);
const hasConfigCreds = hasConfiguredMSTeamsCredentials(cfg.channels?.msteams);
const canUseEnv = Boolean(
!hasConfigCreds &&
normalizeSecretInputString(process.env.MSTEAMS_APP_ID) &&
normalizeSecretInputString(process.env.MSTEAMS_APP_PASSWORD) &&
normalizeSecretInputString(process.env.MSTEAMS_TENANT_ID),
);
let next: OpenClawConfig = cfg;
let appId: string | null = null;
let appPassword: string | null = null;
let tenantId: string | null = null;
if (!resolved && !hasConfigCreds) {
await noteMSTeamsCredentialHelp(prompter);
}
if (canUseEnv) {
const keepEnv = await prompter.confirm({
message:
"MSTEAMS_APP_ID + MSTEAMS_APP_PASSWORD + MSTEAMS_TENANT_ID detected. Use env vars?",
initialValue: true,
});
if (keepEnv) {
next = msteamsSetupAdapter.applyAccountConfig({
cfg: next,
accountId: DEFAULT_ACCOUNT_ID,
input: {},
});
} else {
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
}
} else if (hasConfigCreds) {
const keep = await prompter.confirm({
message: "MS Teams credentials already configured. Keep them?",
initialValue: true,
});
if (!keep) {
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
}
} else {
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
}
if (appId && appPassword && tenantId) {
next = {
...next,
channels: {
...next.channels,
msteams: {
...next.channels?.msteams,
enabled: true,
appId,
appPassword,
tenantId,
},
},
};
}
return { cfg: next, accountId: DEFAULT_ACCOUNT_ID };
},
};
}

View File

@@ -1,6 +1,6 @@
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { msteamsSetupAdapter } from "./setup-core.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMSTeamsSetupWizardBase, msteamsSetupAdapter } from "./setup-core.js";
const resolveMSTeamsUserAllowlist = vi.hoisted(() => vi.fn());
const resolveMSTeamsChannelAllowlist = vi.hoisted(() => vi.fn());
@@ -25,17 +25,8 @@ vi.mock("./token.js", () => ({
resolveMSTeamsCredentials,
}));
vi.mock("../../../src/channels/plugins/bundled.js", () => ({
bundledChannelPlugins: [],
bundledChannelSetupPlugins: [],
}));
describe("msteams setup surface", () => {
let msteamsSetupWizard: typeof import("./setup-surface.js").msteamsSetupWizard;
beforeAll(async () => {
({ msteamsSetupWizard } = await import("./setup-surface.js"));
});
const msteamsSetupWizard = createMSTeamsSetupWizardBase();
beforeEach(() => {
resolveMSTeamsUserAllowlist.mockReset();

View File

@@ -2,9 +2,6 @@ import {
createTopLevelChannelAllowFromSetter,
createTopLevelChannelDmPolicy,
createTopLevelChannelGroupPolicySetter,
createStandardChannelSetupStatus,
DEFAULT_ACCOUNT_ID,
formatDocsLink,
mergeAllowFromEntries,
splitSetupEntries,
type ChannelSetupDmPolicy,
@@ -19,9 +16,8 @@ import {
resolveMSTeamsChannelAllowlist,
resolveMSTeamsUserAllowlist,
} from "./resolve-allowlist.js";
import { normalizeSecretInputString } from "./secret-input.js";
import { msteamsSetupAdapter } from "./setup-core.js";
import { hasConfiguredMSTeamsCredentials, resolveMSTeamsCredentials } from "./token.js";
import { createMSTeamsSetupWizardBase, msteamsSetupAdapter } from "./setup-core.js";
import { resolveMSTeamsCredentials } from "./token.js";
const channel = "msteams" as const;
const setMSTeamsAllowFrom = createTopLevelChannelAllowFromSetter({
@@ -36,32 +32,6 @@ function looksLikeGuid(value: string): boolean {
return /^[0-9a-fA-F-]{16,}$/.test(value);
}
async function promptMSTeamsCredentials(prompter: WizardPrompter): Promise<{
appId: string;
appPassword: string;
tenantId: string;
}> {
const appId = String(
await prompter.text({
message: "Enter MS Teams App ID",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
const appPassword = String(
await prompter.text({
message: "Enter MS Teams App Password",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
const tenantId = String(
await prompter.text({
message: "Enter MS Teams Tenant ID",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
return { appId, appPassword, tenantId };
}
async function promptMSTeamsAllowFrom(params: {
cfg: OpenClawConfig;
prompter: WizardPrompter;
@@ -125,19 +95,6 @@ async function promptMSTeamsAllowFrom(params: {
}
}
async function noteMSTeamsCredentialHelp(prompter: WizardPrompter): Promise<void> {
await prompter.note(
[
"1) Azure Bot registration -> get App ID + Tenant ID",
"2) Add a client secret (App Password)",
"3) Set webhook URL + messaging endpoint",
"Tip: you can also set MSTEAMS_APP_ID / MSTEAMS_APP_PASSWORD / MSTEAMS_TENANT_ID.",
`Docs: ${formatDocsLink("/channels/msteams", "msteams")}`,
].join("\n"),
"MS Teams credentials",
);
}
function setMSTeamsTeamsAllowlist(
cfg: OpenClawConfig,
entries: Array<{ teamKey: string; channelKey?: string }>,
@@ -272,88 +229,10 @@ const msteamsDmPolicy: ChannelSetupDmPolicy = createTopLevelChannelDmPolicy({
export { msteamsSetupAdapter } from "./setup-core.js";
const msteamsSetupWizardBase = createMSTeamsSetupWizardBase();
export const msteamsSetupWizard: ChannelSetupWizard = {
channel,
resolveAccountIdForConfigure: () => DEFAULT_ACCOUNT_ID,
resolveShouldPromptAccountIds: () => false,
status: createStandardChannelSetupStatus({
channelLabel: "MS Teams",
configuredLabel: "configured",
unconfiguredLabel: "needs app credentials",
configuredHint: "configured",
unconfiguredHint: "needs app creds",
configuredScore: 2,
unconfiguredScore: 0,
includeStatusLine: true,
resolveConfigured: ({ cfg }) =>
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)) ||
hasConfiguredMSTeamsCredentials(cfg.channels?.msteams),
}),
credentials: [],
finalize: async ({ cfg, prompter }) => {
const resolved = resolveMSTeamsCredentials(cfg.channels?.msteams);
const hasConfigCreds = hasConfiguredMSTeamsCredentials(cfg.channels?.msteams);
const canUseEnv = Boolean(
!hasConfigCreds &&
normalizeSecretInputString(process.env.MSTEAMS_APP_ID) &&
normalizeSecretInputString(process.env.MSTEAMS_APP_PASSWORD) &&
normalizeSecretInputString(process.env.MSTEAMS_TENANT_ID),
);
let next = cfg;
let appId: string | null = null;
let appPassword: string | null = null;
let tenantId: string | null = null;
if (!resolved && !hasConfigCreds) {
await noteMSTeamsCredentialHelp(prompter);
}
if (canUseEnv) {
const keepEnv = await prompter.confirm({
message:
"MSTEAMS_APP_ID + MSTEAMS_APP_PASSWORD + MSTEAMS_TENANT_ID detected. Use env vars?",
initialValue: true,
});
if (keepEnv) {
next = msteamsSetupAdapter.applyAccountConfig({
cfg: next,
accountId: DEFAULT_ACCOUNT_ID,
input: {},
});
} else {
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
}
} else if (hasConfigCreds) {
const keep = await prompter.confirm({
message: "MS Teams credentials already configured. Keep them?",
initialValue: true,
});
if (!keep) {
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
}
} else {
({ appId, appPassword, tenantId } = await promptMSTeamsCredentials(prompter));
}
if (appId && appPassword && tenantId) {
next = {
...next,
channels: {
...next.channels,
msteams: {
...next.channels?.msteams,
enabled: true,
appId,
appPassword,
tenantId,
},
},
};
}
return { cfg: next, accountId: DEFAULT_ACCOUNT_ID };
},
...msteamsSetupWizardBase,
dmPolicy: msteamsDmPolicy,
groupAccess: msteamsGroupAccess,
disable: (cfg) => ({