test: move setup surface coverage

This commit is contained in:
Peter Steinberger 2026-03-15 22:00:06 -07:00
parent 5c120cb36c
commit f6f0045e0f
No known key found for this signature in database
4 changed files with 398 additions and 0 deletions

View File

@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { parseIMessageAllowFromEntries } from "./setup-surface.js";
describe("parseIMessageAllowFromEntries", () => {
it("parses handles and chat targets", () => {
expect(parseIMessageAllowFromEntries("+15555550123, chat_id:123, chat_guid:abc")).toEqual({
entries: ["+15555550123", "chat_id:123", "chat_guid:abc"],
});
});
it("returns validation errors for invalid chat_id", () => {
expect(parseIMessageAllowFromEntries("chat_id:abc")).toEqual({
entries: [],
error: "Invalid chat_id: chat_id:abc",
});
});
it("returns validation errors for invalid chat_identifier entries", () => {
expect(parseIMessageAllowFromEntries("chat_identifier:")).toEqual({
entries: [],
error: "Invalid chat_identifier entry",
});
});
});

View File

@ -0,0 +1,24 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/mattermost";
import { describe, expect, it } from "vitest";
import { mattermostSetupWizard } from "./setup-surface.js";
describe("mattermost setup status", () => {
it("treats SecretRef botToken as configured when baseUrl is present", async () => {
const configured = await mattermostSetupWizard.status.resolveConfigured({
cfg: {
channels: {
mattermost: {
baseUrl: "https://chat.example.test",
botToken: {
source: "env",
provider: "default",
id: "MATTERMOST_BOT_TOKEN",
},
},
},
} as OpenClawConfig,
});
expect(configured).toBe(true);
});
});

View File

@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { normalizeSignalAccountInput, parseSignalAllowFromEntries } from "./setup-surface.js";
describe("normalizeSignalAccountInput", () => {
it("normalizes valid E.164 numbers", () => {
expect(normalizeSignalAccountInput(" +1 (555) 555-0123 ")).toBe("+15555550123");
});
it("rejects invalid values", () => {
expect(normalizeSignalAccountInput("abc")).toBeNull();
});
});
describe("parseSignalAllowFromEntries", () => {
it("parses e164, uuid and wildcard entries", () => {
expect(
parseSignalAllowFromEntries("+15555550123, uuid:123e4567-e89b-12d3-a456-426614174000, *"),
).toEqual({
entries: ["+15555550123", "uuid:123e4567-e89b-12d3-a456-426614174000", "*"],
});
});
it("normalizes bare uuid values", () => {
expect(parseSignalAllowFromEntries("123e4567-e89b-12d3-a456-426614174000")).toEqual({
entries: ["uuid:123e4567-e89b-12d3-a456-426614174000"],
});
});
it("returns validation errors for invalid entries", () => {
expect(parseSignalAllowFromEntries("uuid:")).toEqual({
entries: [],
error: "Invalid uuid entry",
});
expect(parseSignalAllowFromEntries("invalid")).toEqual({
entries: [],
error: "Invalid entry: invalid",
});
});
});

View File

@ -0,0 +1,311 @@
/**
* Tests for setup-surface.ts helpers.
*
* Tests cover:
* - promptToken helper
* - promptUsername helper
* - promptClientId helper
* - promptChannelName helper
* - promptRefreshTokenSetup helper
* - configureWithEnvToken helper
* - setTwitchAccount config updates
*/
import type { WizardPrompter } from "openclaw/plugin-sdk/twitch";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { TwitchAccountConfig } from "./types.js";
// Mock the helpers we're testing
const mockPromptText = vi.fn();
const mockPromptConfirm = vi.fn();
const mockPrompter: WizardPrompter = {
text: mockPromptText,
confirm: mockPromptConfirm,
} as unknown as WizardPrompter;
const mockAccount: TwitchAccountConfig = {
username: "testbot",
accessToken: "oauth:test123",
clientId: "test-client-id",
channel: "#testchannel",
};
describe("setup surface helpers", () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
// Don't restoreAllMocks as it breaks module-level mocks
});
describe("promptToken", () => {
it("should return existing token when user confirms to keep it", async () => {
const { promptToken } = await import("./setup-surface.js");
mockPromptConfirm.mockResolvedValue(true);
const result = await promptToken(mockPrompter, mockAccount, undefined);
expect(result).toBe("oauth:test123");
expect(mockPromptConfirm).toHaveBeenCalledWith({
message: "Access token already configured. Keep it?",
initialValue: true,
});
expect(mockPromptText).not.toHaveBeenCalled();
});
it("should prompt for new token when user doesn't keep existing", async () => {
const { promptToken } = await import("./setup-surface.js");
mockPromptConfirm.mockResolvedValue(false);
mockPromptText.mockResolvedValue("oauth:newtoken123");
const result = await promptToken(mockPrompter, mockAccount, undefined);
expect(result).toBe("oauth:newtoken123");
expect(mockPromptText).toHaveBeenCalledWith({
message: "Twitch OAuth token (oauth:...)",
initialValue: "",
validate: expect.any(Function),
});
});
it("should use env token as initial value when provided", async () => {
const { promptToken } = await import("./setup-surface.js");
mockPromptConfirm.mockResolvedValue(false);
mockPromptText.mockResolvedValue("oauth:fromenv");
await promptToken(mockPrompter, null, "oauth:fromenv");
expect(mockPromptText).toHaveBeenCalledWith(
expect.objectContaining({
initialValue: "oauth:fromenv",
}),
);
});
it("should validate token format", async () => {
const { promptToken } = await import("./setup-surface.js");
// Set up mocks - user doesn't want to keep existing token
mockPromptConfirm.mockResolvedValueOnce(false);
// Track how many times promptText is called
let promptTextCallCount = 0;
let capturedValidate: ((value: string) => string | undefined) | undefined;
mockPromptText.mockImplementationOnce((_args) => {
promptTextCallCount++;
// Capture the validate function from the first argument
if (_args?.validate) {
capturedValidate = _args.validate;
}
return Promise.resolve("oauth:test123");
});
// Call promptToken
const result = await promptToken(mockPrompter, mockAccount, undefined);
// Verify promptText was called
expect(promptTextCallCount).toBe(1);
expect(result).toBe("oauth:test123");
// Test the validate function
expect(capturedValidate).toBeDefined();
expect(capturedValidate!("")).toBe("Required");
expect(capturedValidate!("notoauth")).toBe("Token should start with 'oauth:'");
});
it("should return early when no existing token and no env token", async () => {
const { promptToken } = await import("./setup-surface.js");
mockPromptText.mockResolvedValue("oauth:newtoken");
const result = await promptToken(mockPrompter, null, undefined);
expect(result).toBe("oauth:newtoken");
expect(mockPromptConfirm).not.toHaveBeenCalled();
});
});
describe("promptUsername", () => {
it("should prompt for username with validation", async () => {
const { promptUsername } = await import("./setup-surface.js");
mockPromptText.mockResolvedValue("mybot");
const result = await promptUsername(mockPrompter, null);
expect(result).toBe("mybot");
expect(mockPromptText).toHaveBeenCalledWith({
message: "Twitch bot username",
initialValue: "",
validate: expect.any(Function),
});
});
it("should use existing username as initial value", async () => {
const { promptUsername } = await import("./setup-surface.js");
mockPromptText.mockResolvedValue("testbot");
await promptUsername(mockPrompter, mockAccount);
expect(mockPromptText).toHaveBeenCalledWith(
expect.objectContaining({
initialValue: "testbot",
}),
);
});
});
describe("promptClientId", () => {
it("should prompt for client ID with validation", async () => {
const { promptClientId } = await import("./setup-surface.js");
mockPromptText.mockResolvedValue("abc123xyz");
const result = await promptClientId(mockPrompter, null);
expect(result).toBe("abc123xyz");
expect(mockPromptText).toHaveBeenCalledWith({
message: "Twitch Client ID",
initialValue: "",
validate: expect.any(Function),
});
});
});
describe("promptChannelName", () => {
it("should return channel name when provided", async () => {
const { promptChannelName } = await import("./setup-surface.js");
mockPromptText.mockResolvedValue("#mychannel");
const result = await promptChannelName(mockPrompter, null);
expect(result).toBe("#mychannel");
});
it("should require a non-empty channel name", async () => {
const { promptChannelName } = await import("./setup-surface.js");
mockPromptText.mockResolvedValue("");
await promptChannelName(mockPrompter, null);
const { validate } = mockPromptText.mock.calls[0]?.[0] ?? {};
expect(validate?.("")).toBe("Required");
expect(validate?.(" ")).toBe("Required");
expect(validate?.("#chan")).toBeUndefined();
});
});
describe("promptRefreshTokenSetup", () => {
it("should return empty object when user declines", async () => {
const { promptRefreshTokenSetup } = await import("./setup-surface.js");
mockPromptConfirm.mockResolvedValue(false);
const result = await promptRefreshTokenSetup(mockPrompter, mockAccount);
expect(result).toEqual({});
expect(mockPromptConfirm).toHaveBeenCalledWith({
message: "Enable automatic token refresh (requires client secret and refresh token)?",
initialValue: false,
});
});
it("should prompt for credentials when user accepts", async () => {
const { promptRefreshTokenSetup } = await import("./setup-surface.js");
mockPromptConfirm
.mockResolvedValueOnce(true) // First call: useRefresh
.mockResolvedValueOnce("secret123") // clientSecret
.mockResolvedValueOnce("refresh123"); // refreshToken
mockPromptText.mockResolvedValueOnce("secret123").mockResolvedValueOnce("refresh123");
const result = await promptRefreshTokenSetup(mockPrompter, null);
expect(result).toEqual({
clientSecret: "secret123",
refreshToken: "refresh123",
});
});
it("should use existing values as initial prompts", async () => {
const { promptRefreshTokenSetup } = await import("./setup-surface.js");
const accountWithRefresh = {
...mockAccount,
clientSecret: "existing-secret",
refreshToken: "existing-refresh",
};
mockPromptConfirm.mockResolvedValue(true);
mockPromptText
.mockResolvedValueOnce("existing-secret")
.mockResolvedValueOnce("existing-refresh");
await promptRefreshTokenSetup(mockPrompter, accountWithRefresh);
expect(mockPromptConfirm).toHaveBeenCalledWith(
expect.objectContaining({
initialValue: true, // Both clientSecret and refreshToken exist
}),
);
});
});
describe("configureWithEnvToken", () => {
it("should return null when user declines env token", async () => {
const { configureWithEnvToken } = await import("./setup-surface.js");
// Reset and set up mock - user declines env token
mockPromptConfirm.mockReset().mockResolvedValue(false as never);
const result = await configureWithEnvToken(
{} as Parameters<typeof configureWithEnvToken>[0],
mockPrompter,
null,
"oauth:fromenv",
false,
{} as Parameters<typeof configureWithEnvToken>[5],
);
// Since user declined, should return null without prompting for username/clientId
expect(result).toBeNull();
expect(mockPromptText).not.toHaveBeenCalled();
});
it("should prompt for username and clientId when using env token", async () => {
const { configureWithEnvToken } = await import("./setup-surface.js");
// Reset and set up mocks - user accepts env token
mockPromptConfirm.mockReset().mockResolvedValue(true as never);
// Set up mocks for username and clientId prompts
mockPromptText
.mockReset()
.mockResolvedValueOnce("testbot" as never)
.mockResolvedValueOnce("test-client-id" as never);
const result = await configureWithEnvToken(
{} as Parameters<typeof configureWithEnvToken>[0],
mockPrompter,
null,
"oauth:fromenv",
false,
{} as Parameters<typeof configureWithEnvToken>[5],
);
// Should return config with username and clientId
expect(result).not.toBeNull();
expect(result?.cfg.channels?.twitch?.accounts?.default?.username).toBe("testbot");
expect(result?.cfg.channels?.twitch?.accounts?.default?.clientId).toBe("test-client-id");
});
});
});