@openclaw/twitch 2026.2.21 → 2026.5.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/api.ts +21 -0
- package/channel-plugin-api.ts +1 -0
- package/index.test.ts +13 -0
- package/index.ts +12 -16
- package/openclaw.plugin.json +6 -0
- package/package.json +29 -7
- package/runtime-api.ts +22 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +3 -0
- package/src/access-control.test.ts +136 -239
- package/src/access-control.ts +11 -4
- package/src/actions.test.ts +74 -0
- package/src/actions.ts +7 -6
- package/src/client-manager-registry.ts +0 -28
- package/src/config-schema.ts +2 -2
- package/src/config.test.ts +147 -1
- package/src/config.ts +76 -15
- package/src/monitor.ts +126 -85
- package/src/outbound.test.ts +140 -75
- package/src/outbound.ts +4 -5
- package/src/plugin.test.ts +39 -1
- package/src/plugin.ts +176 -242
- package/src/probe.test.ts +1 -1
- package/src/probe.ts +16 -5
- package/src/resolver.ts +5 -3
- package/src/runtime.ts +8 -13
- package/src/send.test.ts +92 -59
- package/src/send.ts +18 -15
- package/src/setup-surface.test.ts +511 -0
- package/src/setup-surface.ts +520 -0
- package/src/status.test.ts +120 -153
- package/src/status.ts +1 -1
- package/src/test-fixtures.ts +1 -1
- package/src/token.test.ts +22 -1
- package/src/token.ts +8 -6
- package/src/twitch-client.test.ts +18 -25
- package/src/twitch-client.ts +5 -6
- package/src/types.ts +7 -46
- package/src/utils/twitch.ts +8 -2
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -21
- package/src/onboarding.test.ts +0 -316
- package/src/onboarding.ts +0 -417
package/src/types.ts
CHANGED
|
@@ -5,26 +5,18 @@
|
|
|
5
5
|
* from OpenClaw core.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
9
|
-
ChannelGatewayContext,
|
|
10
|
-
ChannelOutboundAdapter,
|
|
11
|
-
ChannelOutboundContext,
|
|
12
|
-
ChannelResolveKind,
|
|
13
|
-
ChannelResolveResult,
|
|
14
|
-
ChannelStatusAdapter,
|
|
15
|
-
} from "../../../src/channels/plugins/types.adapters.js";
|
|
16
8
|
import type {
|
|
17
9
|
ChannelAccountSnapshot,
|
|
18
|
-
ChannelCapabilities,
|
|
19
10
|
ChannelLogSink,
|
|
20
11
|
ChannelMessageActionAdapter,
|
|
21
12
|
ChannelMessageActionContext,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
ChannelOutboundAdapter,
|
|
14
|
+
ChannelOutboundContext,
|
|
15
|
+
ChannelPlugin,
|
|
16
|
+
ChannelResolveKind,
|
|
17
|
+
ChannelResolveResult,
|
|
18
|
+
OutboundDeliveryResult,
|
|
19
|
+
} from "../runtime-api.js";
|
|
28
20
|
|
|
29
21
|
// ============================================================================
|
|
30
22
|
// Twitch-Specific Types
|
|
@@ -67,16 +59,6 @@ export interface TwitchAccountConfig {
|
|
|
67
59
|
obtainmentTimestamp?: number;
|
|
68
60
|
}
|
|
69
61
|
|
|
70
|
-
/**
|
|
71
|
-
* Message target for Twitch
|
|
72
|
-
*/
|
|
73
|
-
export interface TwitchTarget {
|
|
74
|
-
/** Account ID */
|
|
75
|
-
accountId: string;
|
|
76
|
-
/** Channel name (defaults to account's channel) */
|
|
77
|
-
channel?: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
62
|
/**
|
|
81
63
|
* Twitch message from chat
|
|
82
64
|
*/
|
|
@@ -107,37 +89,16 @@ export interface TwitchChatMessage {
|
|
|
107
89
|
chatType?: "group";
|
|
108
90
|
}
|
|
109
91
|
|
|
110
|
-
/**
|
|
111
|
-
* Send result from Twitch client
|
|
112
|
-
*/
|
|
113
|
-
export interface SendResult {
|
|
114
|
-
ok: boolean;
|
|
115
|
-
error?: string;
|
|
116
|
-
messageId?: string;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
92
|
// Re-export core types for convenience
|
|
120
93
|
export type {
|
|
121
94
|
ChannelAccountSnapshot,
|
|
122
|
-
ChannelGatewayContext,
|
|
123
95
|
ChannelLogSink,
|
|
124
96
|
ChannelMessageActionAdapter,
|
|
125
97
|
ChannelMessageActionContext,
|
|
126
|
-
ChannelMeta,
|
|
127
98
|
ChannelOutboundAdapter,
|
|
128
|
-
ChannelStatusAdapter,
|
|
129
|
-
ChannelCapabilities,
|
|
130
99
|
ChannelResolveKind,
|
|
131
100
|
ChannelResolveResult,
|
|
132
101
|
ChannelPlugin,
|
|
133
102
|
ChannelOutboundContext,
|
|
134
103
|
OutboundDeliveryResult,
|
|
135
104
|
};
|
|
136
|
-
|
|
137
|
-
import type { z } from "zod";
|
|
138
|
-
// Import and re-export the schema type
|
|
139
|
-
import type { TwitchConfigSchema } from "./config-schema.js";
|
|
140
|
-
export type TwitchConfig = z.infer<typeof TwitchConfigSchema>;
|
|
141
|
-
|
|
142
|
-
export type { OpenClawConfig };
|
|
143
|
-
export type { RuntimeEnv };
|
package/src/utils/twitch.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Twitch-specific utility functions
|
|
3
5
|
*/
|
|
4
6
|
|
|
7
|
+
function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
|
8
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
/**
|
|
6
12
|
* Normalize Twitch channel names.
|
|
7
13
|
*
|
|
@@ -16,7 +22,7 @@
|
|
|
16
22
|
* normalizeTwitchChannel("MyChannel") // "mychannel"
|
|
17
23
|
*/
|
|
18
24
|
export function normalizeTwitchChannel(channel: string): string {
|
|
19
|
-
const trimmed = channel
|
|
25
|
+
const trimmed = normalizeLowercaseStringOrEmpty(channel);
|
|
20
26
|
return trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -40,7 +46,7 @@ export function missingTargetError(provider: string, hint?: string): Error {
|
|
|
40
46
|
* @returns A unique message ID
|
|
41
47
|
*/
|
|
42
48
|
export function generateMessageId(): string {
|
|
43
|
-
return `${Date.now()}-${
|
|
49
|
+
return `${Date.now()}-${randomUUID()}`;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.package-boundary.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "."
|
|
5
|
+
},
|
|
6
|
+
"include": ["./*.ts", "./src/**/*.ts"],
|
|
7
|
+
"exclude": [
|
|
8
|
+
"./**/*.test.ts",
|
|
9
|
+
"./dist/**",
|
|
10
|
+
"./node_modules/**",
|
|
11
|
+
"./src/test-support/**",
|
|
12
|
+
"./src/**/*test-helpers.ts",
|
|
13
|
+
"./src/**/*test-harness.ts",
|
|
14
|
+
"./src/**/*test-support.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 2026.1.23
|
|
4
|
-
|
|
5
|
-
### Features
|
|
6
|
-
|
|
7
|
-
- Initial Twitch plugin release
|
|
8
|
-
- Twitch chat integration via @twurple (IRC connection)
|
|
9
|
-
- Multi-account support with per-channel configuration
|
|
10
|
-
- Access control via user ID allowlists and role-based restrictions
|
|
11
|
-
- Automatic token refresh with RefreshingAuthProvider
|
|
12
|
-
- Environment variable fallback for default account token
|
|
13
|
-
- Message actions support
|
|
14
|
-
- Status monitoring and probing
|
|
15
|
-
- Outbound message delivery with markdown stripping
|
|
16
|
-
|
|
17
|
-
### Improvements
|
|
18
|
-
|
|
19
|
-
- Added proper configuration schema with Zod validation
|
|
20
|
-
- Added plugin descriptor (openclaw.plugin.json)
|
|
21
|
-
- Added comprehensive README and documentation
|
package/src/onboarding.test.ts
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for onboarding.ts helpers
|
|
3
|
-
*
|
|
4
|
-
* Tests cover:
|
|
5
|
-
* - promptToken helper
|
|
6
|
-
* - promptUsername helper
|
|
7
|
-
* - promptClientId helper
|
|
8
|
-
* - promptChannelName helper
|
|
9
|
-
* - promptRefreshTokenSetup helper
|
|
10
|
-
* - configureWithEnvToken helper
|
|
11
|
-
* - setTwitchAccount config updates
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { WizardPrompter } from "openclaw/plugin-sdk";
|
|
15
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
16
|
-
import type { TwitchAccountConfig } from "./types.js";
|
|
17
|
-
|
|
18
|
-
vi.mock("openclaw/plugin-sdk", () => ({
|
|
19
|
-
formatDocsLink: (url: string, fallback: string) => fallback || url,
|
|
20
|
-
promptChannelAccessConfig: vi.fn(async () => null),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
// Mock the helpers we're testing
|
|
24
|
-
const mockPromptText = vi.fn();
|
|
25
|
-
const mockPromptConfirm = vi.fn();
|
|
26
|
-
const mockPrompter: WizardPrompter = {
|
|
27
|
-
text: mockPromptText,
|
|
28
|
-
confirm: mockPromptConfirm,
|
|
29
|
-
} as unknown as WizardPrompter;
|
|
30
|
-
|
|
31
|
-
const mockAccount: TwitchAccountConfig = {
|
|
32
|
-
username: "testbot",
|
|
33
|
-
accessToken: "oauth:test123",
|
|
34
|
-
clientId: "test-client-id",
|
|
35
|
-
channel: "#testchannel",
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
describe("onboarding helpers", () => {
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
vi.clearAllMocks();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
// Don't restoreAllMocks as it breaks module-level mocks
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("promptToken", () => {
|
|
48
|
-
it("should return existing token when user confirms to keep it", async () => {
|
|
49
|
-
const { promptToken } = await import("./onboarding.js");
|
|
50
|
-
|
|
51
|
-
mockPromptConfirm.mockResolvedValue(true);
|
|
52
|
-
|
|
53
|
-
const result = await promptToken(mockPrompter, mockAccount, undefined);
|
|
54
|
-
|
|
55
|
-
expect(result).toBe("oauth:test123");
|
|
56
|
-
expect(mockPromptConfirm).toHaveBeenCalledWith({
|
|
57
|
-
message: "Access token already configured. Keep it?",
|
|
58
|
-
initialValue: true,
|
|
59
|
-
});
|
|
60
|
-
expect(mockPromptText).not.toHaveBeenCalled();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should prompt for new token when user doesn't keep existing", async () => {
|
|
64
|
-
const { promptToken } = await import("./onboarding.js");
|
|
65
|
-
|
|
66
|
-
mockPromptConfirm.mockResolvedValue(false);
|
|
67
|
-
mockPromptText.mockResolvedValue("oauth:newtoken123");
|
|
68
|
-
|
|
69
|
-
const result = await promptToken(mockPrompter, mockAccount, undefined);
|
|
70
|
-
|
|
71
|
-
expect(result).toBe("oauth:newtoken123");
|
|
72
|
-
expect(mockPromptText).toHaveBeenCalledWith({
|
|
73
|
-
message: "Twitch OAuth token (oauth:...)",
|
|
74
|
-
initialValue: "",
|
|
75
|
-
validate: expect.any(Function),
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("should use env token as initial value when provided", async () => {
|
|
80
|
-
const { promptToken } = await import("./onboarding.js");
|
|
81
|
-
|
|
82
|
-
mockPromptConfirm.mockResolvedValue(false);
|
|
83
|
-
mockPromptText.mockResolvedValue("oauth:fromenv");
|
|
84
|
-
|
|
85
|
-
await promptToken(mockPrompter, null, "oauth:fromenv");
|
|
86
|
-
|
|
87
|
-
expect(mockPromptText).toHaveBeenCalledWith(
|
|
88
|
-
expect.objectContaining({
|
|
89
|
-
initialValue: "oauth:fromenv",
|
|
90
|
-
}),
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("should validate token format", async () => {
|
|
95
|
-
const { promptToken } = await import("./onboarding.js");
|
|
96
|
-
|
|
97
|
-
// Set up mocks - user doesn't want to keep existing token
|
|
98
|
-
mockPromptConfirm.mockResolvedValueOnce(false);
|
|
99
|
-
|
|
100
|
-
// Track how many times promptText is called
|
|
101
|
-
let promptTextCallCount = 0;
|
|
102
|
-
let capturedValidate: ((value: string) => string | undefined) | undefined;
|
|
103
|
-
|
|
104
|
-
mockPromptText.mockImplementationOnce((_args) => {
|
|
105
|
-
promptTextCallCount++;
|
|
106
|
-
// Capture the validate function from the first argument
|
|
107
|
-
if (_args?.validate) {
|
|
108
|
-
capturedValidate = _args.validate;
|
|
109
|
-
}
|
|
110
|
-
return Promise.resolve("oauth:test123");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Call promptToken
|
|
114
|
-
const result = await promptToken(mockPrompter, mockAccount, undefined);
|
|
115
|
-
|
|
116
|
-
// Verify promptText was called
|
|
117
|
-
expect(promptTextCallCount).toBe(1);
|
|
118
|
-
expect(result).toBe("oauth:test123");
|
|
119
|
-
|
|
120
|
-
// Test the validate function
|
|
121
|
-
expect(capturedValidate).toBeDefined();
|
|
122
|
-
expect(capturedValidate!("")).toBe("Required");
|
|
123
|
-
expect(capturedValidate!("notoauth")).toBe("Token should start with 'oauth:'");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("should return early when no existing token and no env token", async () => {
|
|
127
|
-
const { promptToken } = await import("./onboarding.js");
|
|
128
|
-
|
|
129
|
-
mockPromptText.mockResolvedValue("oauth:newtoken");
|
|
130
|
-
|
|
131
|
-
const result = await promptToken(mockPrompter, null, undefined);
|
|
132
|
-
|
|
133
|
-
expect(result).toBe("oauth:newtoken");
|
|
134
|
-
expect(mockPromptConfirm).not.toHaveBeenCalled();
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe("promptUsername", () => {
|
|
139
|
-
it("should prompt for username with validation", async () => {
|
|
140
|
-
const { promptUsername } = await import("./onboarding.js");
|
|
141
|
-
|
|
142
|
-
mockPromptText.mockResolvedValue("mybot");
|
|
143
|
-
|
|
144
|
-
const result = await promptUsername(mockPrompter, null);
|
|
145
|
-
|
|
146
|
-
expect(result).toBe("mybot");
|
|
147
|
-
expect(mockPromptText).toHaveBeenCalledWith({
|
|
148
|
-
message: "Twitch bot username",
|
|
149
|
-
initialValue: "",
|
|
150
|
-
validate: expect.any(Function),
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("should use existing username as initial value", async () => {
|
|
155
|
-
const { promptUsername } = await import("./onboarding.js");
|
|
156
|
-
|
|
157
|
-
mockPromptText.mockResolvedValue("testbot");
|
|
158
|
-
|
|
159
|
-
await promptUsername(mockPrompter, mockAccount);
|
|
160
|
-
|
|
161
|
-
expect(mockPromptText).toHaveBeenCalledWith(
|
|
162
|
-
expect.objectContaining({
|
|
163
|
-
initialValue: "testbot",
|
|
164
|
-
}),
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe("promptClientId", () => {
|
|
170
|
-
it("should prompt for client ID with validation", async () => {
|
|
171
|
-
const { promptClientId } = await import("./onboarding.js");
|
|
172
|
-
|
|
173
|
-
mockPromptText.mockResolvedValue("abc123xyz");
|
|
174
|
-
|
|
175
|
-
const result = await promptClientId(mockPrompter, null);
|
|
176
|
-
|
|
177
|
-
expect(result).toBe("abc123xyz");
|
|
178
|
-
expect(mockPromptText).toHaveBeenCalledWith({
|
|
179
|
-
message: "Twitch Client ID",
|
|
180
|
-
initialValue: "",
|
|
181
|
-
validate: expect.any(Function),
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
describe("promptChannelName", () => {
|
|
187
|
-
it("should return channel name when provided", async () => {
|
|
188
|
-
const { promptChannelName } = await import("./onboarding.js");
|
|
189
|
-
|
|
190
|
-
mockPromptText.mockResolvedValue("#mychannel");
|
|
191
|
-
|
|
192
|
-
const result = await promptChannelName(mockPrompter, null);
|
|
193
|
-
|
|
194
|
-
expect(result).toBe("#mychannel");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("should require a non-empty channel name", async () => {
|
|
198
|
-
const { promptChannelName } = await import("./onboarding.js");
|
|
199
|
-
|
|
200
|
-
mockPromptText.mockResolvedValue("");
|
|
201
|
-
|
|
202
|
-
await promptChannelName(mockPrompter, null);
|
|
203
|
-
|
|
204
|
-
const { validate } = mockPromptText.mock.calls[0]?.[0] ?? {};
|
|
205
|
-
expect(validate?.("")).toBe("Required");
|
|
206
|
-
expect(validate?.(" ")).toBe("Required");
|
|
207
|
-
expect(validate?.("#chan")).toBeUndefined();
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe("promptRefreshTokenSetup", () => {
|
|
212
|
-
it("should return empty object when user declines", async () => {
|
|
213
|
-
const { promptRefreshTokenSetup } = await import("./onboarding.js");
|
|
214
|
-
|
|
215
|
-
mockPromptConfirm.mockResolvedValue(false);
|
|
216
|
-
|
|
217
|
-
const result = await promptRefreshTokenSetup(mockPrompter, mockAccount);
|
|
218
|
-
|
|
219
|
-
expect(result).toEqual({});
|
|
220
|
-
expect(mockPromptConfirm).toHaveBeenCalledWith({
|
|
221
|
-
message: "Enable automatic token refresh (requires client secret and refresh token)?",
|
|
222
|
-
initialValue: false,
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("should prompt for credentials when user accepts", async () => {
|
|
227
|
-
const { promptRefreshTokenSetup } = await import("./onboarding.js");
|
|
228
|
-
|
|
229
|
-
mockPromptConfirm
|
|
230
|
-
.mockResolvedValueOnce(true) // First call: useRefresh
|
|
231
|
-
.mockResolvedValueOnce("secret123") // clientSecret
|
|
232
|
-
.mockResolvedValueOnce("refresh123"); // refreshToken
|
|
233
|
-
|
|
234
|
-
mockPromptText.mockResolvedValueOnce("secret123").mockResolvedValueOnce("refresh123");
|
|
235
|
-
|
|
236
|
-
const result = await promptRefreshTokenSetup(mockPrompter, null);
|
|
237
|
-
|
|
238
|
-
expect(result).toEqual({
|
|
239
|
-
clientSecret: "secret123",
|
|
240
|
-
refreshToken: "refresh123",
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("should use existing values as initial prompts", async () => {
|
|
245
|
-
const { promptRefreshTokenSetup } = await import("./onboarding.js");
|
|
246
|
-
|
|
247
|
-
const accountWithRefresh = {
|
|
248
|
-
...mockAccount,
|
|
249
|
-
clientSecret: "existing-secret",
|
|
250
|
-
refreshToken: "existing-refresh",
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
mockPromptConfirm.mockResolvedValue(true);
|
|
254
|
-
mockPromptText
|
|
255
|
-
.mockResolvedValueOnce("existing-secret")
|
|
256
|
-
.mockResolvedValueOnce("existing-refresh");
|
|
257
|
-
|
|
258
|
-
await promptRefreshTokenSetup(mockPrompter, accountWithRefresh);
|
|
259
|
-
|
|
260
|
-
expect(mockPromptConfirm).toHaveBeenCalledWith(
|
|
261
|
-
expect.objectContaining({
|
|
262
|
-
initialValue: true, // Both clientSecret and refreshToken exist
|
|
263
|
-
}),
|
|
264
|
-
);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
describe("configureWithEnvToken", () => {
|
|
269
|
-
it("should return null when user declines env token", async () => {
|
|
270
|
-
const { configureWithEnvToken } = await import("./onboarding.js");
|
|
271
|
-
|
|
272
|
-
// Reset and set up mock - user declines env token
|
|
273
|
-
mockPromptConfirm.mockReset().mockResolvedValue(false as never);
|
|
274
|
-
|
|
275
|
-
const result = await configureWithEnvToken(
|
|
276
|
-
{} as Parameters<typeof configureWithEnvToken>[0],
|
|
277
|
-
mockPrompter,
|
|
278
|
-
null,
|
|
279
|
-
"oauth:fromenv",
|
|
280
|
-
false,
|
|
281
|
-
{} as Parameters<typeof configureWithEnvToken>[5],
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
// Since user declined, should return null without prompting for username/clientId
|
|
285
|
-
expect(result).toBeNull();
|
|
286
|
-
expect(mockPromptText).not.toHaveBeenCalled();
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("should prompt for username and clientId when using env token", async () => {
|
|
290
|
-
const { configureWithEnvToken } = await import("./onboarding.js");
|
|
291
|
-
|
|
292
|
-
// Reset and set up mocks - user accepts env token
|
|
293
|
-
mockPromptConfirm.mockReset().mockResolvedValue(true as never);
|
|
294
|
-
|
|
295
|
-
// Set up mocks for username and clientId prompts
|
|
296
|
-
mockPromptText
|
|
297
|
-
.mockReset()
|
|
298
|
-
.mockResolvedValueOnce("testbot" as never)
|
|
299
|
-
.mockResolvedValueOnce("test-client-id" as never);
|
|
300
|
-
|
|
301
|
-
const result = await configureWithEnvToken(
|
|
302
|
-
{} as Parameters<typeof configureWithEnvToken>[0],
|
|
303
|
-
mockPrompter,
|
|
304
|
-
null,
|
|
305
|
-
"oauth:fromenv",
|
|
306
|
-
false,
|
|
307
|
-
{} as Parameters<typeof configureWithEnvToken>[5],
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
// Should return config with username and clientId
|
|
311
|
-
expect(result).not.toBeNull();
|
|
312
|
-
expect(result?.cfg.channels?.twitch?.accounts?.default?.username).toBe("testbot");
|
|
313
|
-
expect(result?.cfg.channels?.twitch?.accounts?.default?.clientId).toBe("test-client-id");
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
});
|