@herdctl/discord 0.0.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/dist/__tests__/auto-mode-handler.test.d.ts +2 -0
- package/dist/__tests__/auto-mode-handler.test.d.ts.map +1 -0
- package/dist/__tests__/auto-mode-handler.test.js +362 -0
- package/dist/__tests__/auto-mode-handler.test.js.map +1 -0
- package/dist/__tests__/discord-connector.test.d.ts +2 -0
- package/dist/__tests__/discord-connector.test.d.ts.map +1 -0
- package/dist/__tests__/discord-connector.test.js +958 -0
- package/dist/__tests__/discord-connector.test.js.map +1 -0
- package/dist/__tests__/error-handler.test.d.ts +2 -0
- package/dist/__tests__/error-handler.test.d.ts.map +1 -0
- package/dist/__tests__/error-handler.test.js +509 -0
- package/dist/__tests__/error-handler.test.js.map +1 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +152 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +282 -0
- package/dist/__tests__/logger.test.js.map +1 -0
- package/dist/__tests__/mention-handler.test.d.ts +2 -0
- package/dist/__tests__/mention-handler.test.d.ts.map +1 -0
- package/dist/__tests__/mention-handler.test.js +547 -0
- package/dist/__tests__/mention-handler.test.js.map +1 -0
- package/dist/auto-mode-handler.d.ts +145 -0
- package/dist/auto-mode-handler.d.ts.map +1 -0
- package/dist/auto-mode-handler.js +211 -0
- package/dist/auto-mode-handler.js.map +1 -0
- package/dist/commands/__tests__/command-manager.test.d.ts +2 -0
- package/dist/commands/__tests__/command-manager.test.d.ts.map +1 -0
- package/dist/commands/__tests__/command-manager.test.js +307 -0
- package/dist/commands/__tests__/command-manager.test.js.map +1 -0
- package/dist/commands/__tests__/help.test.d.ts +2 -0
- package/dist/commands/__tests__/help.test.d.ts.map +1 -0
- package/dist/commands/__tests__/help.test.js +105 -0
- package/dist/commands/__tests__/help.test.js.map +1 -0
- package/dist/commands/__tests__/reset.test.d.ts +2 -0
- package/dist/commands/__tests__/reset.test.d.ts.map +1 -0
- package/dist/commands/__tests__/reset.test.js +140 -0
- package/dist/commands/__tests__/reset.test.js.map +1 -0
- package/dist/commands/__tests__/status.test.d.ts +2 -0
- package/dist/commands/__tests__/status.test.d.ts.map +1 -0
- package/dist/commands/__tests__/status.test.js +205 -0
- package/dist/commands/__tests__/status.test.js.map +1 -0
- package/dist/commands/command-manager.d.ts +66 -0
- package/dist/commands/command-manager.d.ts.map +1 -0
- package/dist/commands/command-manager.js +191 -0
- package/dist/commands/command-manager.js.map +1 -0
- package/dist/commands/help.d.ts +8 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +27 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/index.d.ts +12 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +13 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/reset.d.ts +9 -0
- package/dist/commands/reset.d.ts.map +1 -0
- package/dist/commands/reset.js +28 -0
- package/dist/commands/reset.js.map +1 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +102 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/types.d.ts +87 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +8 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/discord-connector.d.ts +154 -0
- package/dist/discord-connector.d.ts.map +1 -0
- package/dist/discord-connector.js +638 -0
- package/dist/discord-connector.js.map +1 -0
- package/dist/error-handler.d.ts +237 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +433 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +77 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +119 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +198 -0
- package/dist/logger.js.map +1 -0
- package/dist/mention-handler.d.ts +176 -0
- package/dist/mention-handler.d.ts.map +1 -0
- package/dist/mention-handler.js +236 -0
- package/dist/mention-handler.js.map +1 -0
- package/dist/session-manager/__tests__/errors.test.d.ts +2 -0
- package/dist/session-manager/__tests__/errors.test.d.ts.map +1 -0
- package/dist/session-manager/__tests__/errors.test.js +124 -0
- package/dist/session-manager/__tests__/errors.test.js.map +1 -0
- package/dist/session-manager/__tests__/session-manager.test.d.ts +2 -0
- package/dist/session-manager/__tests__/session-manager.test.d.ts.map +1 -0
- package/dist/session-manager/__tests__/session-manager.test.js +517 -0
- package/dist/session-manager/__tests__/session-manager.test.js.map +1 -0
- package/dist/session-manager/__tests__/types.test.d.ts +2 -0
- package/dist/session-manager/__tests__/types.test.d.ts.map +1 -0
- package/dist/session-manager/__tests__/types.test.js +169 -0
- package/dist/session-manager/__tests__/types.test.js.map +1 -0
- package/dist/session-manager/errors.d.ts +58 -0
- package/dist/session-manager/errors.d.ts.map +1 -0
- package/dist/session-manager/errors.js +70 -0
- package/dist/session-manager/errors.js.map +1 -0
- package/dist/session-manager/index.d.ts +11 -0
- package/dist/session-manager/index.d.ts.map +1 -0
- package/dist/session-manager/index.js +12 -0
- package/dist/session-manager/index.js.map +1 -0
- package/dist/session-manager/session-manager.d.ts +107 -0
- package/dist/session-manager/session-manager.d.ts.map +1 -0
- package/dist/session-manager/session-manager.js +347 -0
- package/dist/session-manager/session-manager.js.map +1 -0
- package/dist/session-manager/types.d.ts +167 -0
- package/dist/session-manager/types.d.ts.map +1 -0
- package/dist/session-manager/types.js +57 -0
- package/dist/session-manager/types.js.map +1 -0
- package/dist/types.d.ts +323 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/__tests__/formatting.test.d.ts +2 -0
- package/dist/utils/__tests__/formatting.test.d.ts.map +1 -0
- package/dist/utils/__tests__/formatting.test.js +571 -0
- package/dist/utils/__tests__/formatting.test.js.map +1 -0
- package/dist/utils/formatting.d.ts +211 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +305 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Mock discord.js - Must be hoisted, factory cannot reference external variables
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Shared mock state - these will be accessed by tests
|
|
6
|
+
let mockLoginImpl = null;
|
|
7
|
+
// Mock REST EventEmitter for rate limit events
|
|
8
|
+
let mockRestEmitter = null;
|
|
9
|
+
// Mock discord.js module - factory must be self-contained
|
|
10
|
+
vi.mock("discord.js", () => {
|
|
11
|
+
const { EventEmitter } = require("events");
|
|
12
|
+
// Define mock user inside factory
|
|
13
|
+
const mockUser = {
|
|
14
|
+
id: "123456789",
|
|
15
|
+
username: "TestBot",
|
|
16
|
+
discriminator: "0001",
|
|
17
|
+
setActivity: vi.fn(),
|
|
18
|
+
};
|
|
19
|
+
// Define mock client inside factory
|
|
20
|
+
class MockClientClass extends EventEmitter {
|
|
21
|
+
user = mockUser;
|
|
22
|
+
rest = new EventEmitter(); // REST client for rate limit events
|
|
23
|
+
login = vi.fn().mockImplementation(async () => {
|
|
24
|
+
// Use the external mockLoginImpl if set, otherwise return success
|
|
25
|
+
if (mockLoginImpl) {
|
|
26
|
+
return mockLoginImpl();
|
|
27
|
+
}
|
|
28
|
+
return "token";
|
|
29
|
+
});
|
|
30
|
+
destroy = vi.fn();
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
// Store reference to rest emitter for test access
|
|
34
|
+
mockRestEmitter = this.rest;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
Client: MockClientClass,
|
|
39
|
+
GatewayIntentBits: {
|
|
40
|
+
Guilds: 1,
|
|
41
|
+
GuildMessages: 2,
|
|
42
|
+
DirectMessages: 4,
|
|
43
|
+
MessageContent: 8,
|
|
44
|
+
},
|
|
45
|
+
Events: {
|
|
46
|
+
ClientReady: "ready",
|
|
47
|
+
ShardDisconnect: "shardDisconnect",
|
|
48
|
+
ShardReconnecting: "shardReconnecting",
|
|
49
|
+
ShardResume: "shardResume",
|
|
50
|
+
Error: "error",
|
|
51
|
+
Warn: "warn",
|
|
52
|
+
Debug: "debug",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
// Mock @discordjs/rest for RESTEvents constant
|
|
57
|
+
vi.mock("@discordjs/rest", () => {
|
|
58
|
+
return {
|
|
59
|
+
RESTEvents: {
|
|
60
|
+
RateLimited: "rateLimited",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
// Import after mock
|
|
65
|
+
import { DiscordConnector } from "../discord-connector.js";
|
|
66
|
+
import { AlreadyConnectedError, DiscordConnectionError, InvalidTokenError, } from "../errors.js";
|
|
67
|
+
// Helper to set the login behavior before connecting
|
|
68
|
+
function setMockLoginBehavior(behavior, errorMessage) {
|
|
69
|
+
if (behavior === "failure") {
|
|
70
|
+
mockLoginImpl = () => Promise.reject(new Error(errorMessage || "Login failed"));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
mockLoginImpl = null; // Use default success behavior
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// Test Fixtures
|
|
78
|
+
// =============================================================================
|
|
79
|
+
function createMockAgentConfig() {
|
|
80
|
+
return {
|
|
81
|
+
name: "test-agent",
|
|
82
|
+
description: "Test agent for Discord connector tests",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createMockDiscordConfig() {
|
|
86
|
+
return {
|
|
87
|
+
bot_token_env: "TEST_BOT_TOKEN",
|
|
88
|
+
session_expiry_hours: 24,
|
|
89
|
+
log_level: "standard",
|
|
90
|
+
guilds: [
|
|
91
|
+
{
|
|
92
|
+
id: "guild-123",
|
|
93
|
+
channels: [
|
|
94
|
+
{
|
|
95
|
+
id: "channel-456",
|
|
96
|
+
name: "#test",
|
|
97
|
+
mode: "mention",
|
|
98
|
+
context_messages: 10,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function createMockFleetManager() {
|
|
106
|
+
return {
|
|
107
|
+
trigger: vi.fn(),
|
|
108
|
+
getFleetStatus: vi.fn(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createMockSessionManager() {
|
|
112
|
+
return {
|
|
113
|
+
agentName: "test-agent",
|
|
114
|
+
getOrCreateSession: vi.fn().mockResolvedValue({ sessionId: "test-session", isNew: true }),
|
|
115
|
+
touchSession: vi.fn().mockResolvedValue(undefined),
|
|
116
|
+
getSession: vi.fn().mockResolvedValue(null),
|
|
117
|
+
clearSession: vi.fn().mockResolvedValue(true),
|
|
118
|
+
cleanupExpiredSessions: vi.fn().mockResolvedValue(0),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function createMockLogger() {
|
|
122
|
+
return {
|
|
123
|
+
debug: vi.fn(),
|
|
124
|
+
info: vi.fn(),
|
|
125
|
+
warn: vi.fn(),
|
|
126
|
+
error: vi.fn(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// =============================================================================
|
|
130
|
+
// Constructor Tests
|
|
131
|
+
// =============================================================================
|
|
132
|
+
describe("DiscordConnector", () => {
|
|
133
|
+
let agentConfig;
|
|
134
|
+
let discordConfig;
|
|
135
|
+
let fleetManager;
|
|
136
|
+
let sessionManager;
|
|
137
|
+
let mockLogger;
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
vi.clearAllMocks();
|
|
140
|
+
setMockLoginBehavior("success"); // Reset to success by default
|
|
141
|
+
agentConfig = createMockAgentConfig();
|
|
142
|
+
discordConfig = createMockDiscordConfig();
|
|
143
|
+
fleetManager = createMockFleetManager();
|
|
144
|
+
sessionManager = createMockSessionManager();
|
|
145
|
+
mockLogger = createMockLogger();
|
|
146
|
+
});
|
|
147
|
+
describe("constructor", () => {
|
|
148
|
+
it("creates connector with valid options", () => {
|
|
149
|
+
const connector = new DiscordConnector({
|
|
150
|
+
agentConfig,
|
|
151
|
+
discordConfig,
|
|
152
|
+
botToken: "valid-token",
|
|
153
|
+
fleetManager,
|
|
154
|
+
sessionManager,
|
|
155
|
+
logger: mockLogger,
|
|
156
|
+
});
|
|
157
|
+
expect(connector.agentName).toBe("test-agent");
|
|
158
|
+
expect(connector.isConnected()).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
it("throws InvalidTokenError for empty token", () => {
|
|
161
|
+
expect(() => new DiscordConnector({
|
|
162
|
+
agentConfig,
|
|
163
|
+
discordConfig,
|
|
164
|
+
botToken: "",
|
|
165
|
+
fleetManager,
|
|
166
|
+
sessionManager,
|
|
167
|
+
})).toThrow(InvalidTokenError);
|
|
168
|
+
});
|
|
169
|
+
it("throws InvalidTokenError for whitespace-only token", () => {
|
|
170
|
+
expect(() => new DiscordConnector({
|
|
171
|
+
agentConfig,
|
|
172
|
+
discordConfig,
|
|
173
|
+
botToken: " ",
|
|
174
|
+
fleetManager,
|
|
175
|
+
sessionManager,
|
|
176
|
+
})).toThrow(InvalidTokenError);
|
|
177
|
+
});
|
|
178
|
+
it("uses default logger when none provided", () => {
|
|
179
|
+
// Should not throw
|
|
180
|
+
const connector = new DiscordConnector({
|
|
181
|
+
agentConfig,
|
|
182
|
+
discordConfig,
|
|
183
|
+
botToken: "valid-token",
|
|
184
|
+
fleetManager,
|
|
185
|
+
sessionManager,
|
|
186
|
+
});
|
|
187
|
+
expect(connector.agentName).toBe("test-agent");
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// getState Tests
|
|
192
|
+
// =============================================================================
|
|
193
|
+
describe("getState", () => {
|
|
194
|
+
it("returns initial disconnected state", () => {
|
|
195
|
+
const connector = new DiscordConnector({
|
|
196
|
+
agentConfig,
|
|
197
|
+
discordConfig,
|
|
198
|
+
botToken: "valid-token",
|
|
199
|
+
fleetManager,
|
|
200
|
+
sessionManager,
|
|
201
|
+
logger: mockLogger,
|
|
202
|
+
});
|
|
203
|
+
const state = connector.getState();
|
|
204
|
+
expect(state.status).toBe("disconnected");
|
|
205
|
+
expect(state.connectedAt).toBeNull();
|
|
206
|
+
expect(state.disconnectedAt).toBeNull();
|
|
207
|
+
expect(state.reconnectAttempts).toBe(0);
|
|
208
|
+
expect(state.lastError).toBeNull();
|
|
209
|
+
expect(state.botUser).toBeNull();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// connect Tests
|
|
214
|
+
// =============================================================================
|
|
215
|
+
describe("connect", () => {
|
|
216
|
+
it("connects successfully and updates state", async () => {
|
|
217
|
+
const connector = new DiscordConnector({
|
|
218
|
+
agentConfig,
|
|
219
|
+
discordConfig,
|
|
220
|
+
botToken: "valid-token",
|
|
221
|
+
fleetManager,
|
|
222
|
+
sessionManager,
|
|
223
|
+
logger: mockLogger,
|
|
224
|
+
});
|
|
225
|
+
const connectPromise = connector.connect();
|
|
226
|
+
// Wait for client to be created
|
|
227
|
+
await vi.waitFor(() => {
|
|
228
|
+
expect(connector.client).not.toBeNull();
|
|
229
|
+
});
|
|
230
|
+
// Get the actual mock client and simulate ready event
|
|
231
|
+
const client = connector.client;
|
|
232
|
+
client.login.mockResolvedValue("token");
|
|
233
|
+
// Emit ready event
|
|
234
|
+
client.emit("ready", { user: client.user });
|
|
235
|
+
await connectPromise;
|
|
236
|
+
expect(connector.isConnected()).toBe(true);
|
|
237
|
+
const state = connector.getState();
|
|
238
|
+
expect(state.status).toBe("connected");
|
|
239
|
+
expect(state.connectedAt).not.toBeNull();
|
|
240
|
+
expect(state.botUser).toEqual({
|
|
241
|
+
id: "123456789",
|
|
242
|
+
username: "TestBot",
|
|
243
|
+
discriminator: "0001",
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
it("emits ready event on successful connection", async () => {
|
|
247
|
+
const connector = new DiscordConnector({
|
|
248
|
+
agentConfig,
|
|
249
|
+
discordConfig,
|
|
250
|
+
botToken: "valid-token",
|
|
251
|
+
fleetManager,
|
|
252
|
+
sessionManager,
|
|
253
|
+
logger: mockLogger,
|
|
254
|
+
});
|
|
255
|
+
const readyHandler = vi.fn();
|
|
256
|
+
connector.on("ready", readyHandler);
|
|
257
|
+
const connectPromise = connector.connect();
|
|
258
|
+
await vi.waitFor(() => {
|
|
259
|
+
expect(connector.client).not.toBeNull();
|
|
260
|
+
});
|
|
261
|
+
const client = connector.client;
|
|
262
|
+
client.login.mockResolvedValue("token");
|
|
263
|
+
client.emit("ready", { user: client.user });
|
|
264
|
+
await connectPromise;
|
|
265
|
+
// Wait for the async ready handler to complete
|
|
266
|
+
await vi.waitFor(() => {
|
|
267
|
+
expect(readyHandler).toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
expect(readyHandler).toHaveBeenCalledWith({
|
|
270
|
+
agentName: "test-agent",
|
|
271
|
+
botUser: {
|
|
272
|
+
id: "123456789",
|
|
273
|
+
username: "TestBot",
|
|
274
|
+
discriminator: "0001",
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
it("throws AlreadyConnectedError when connecting while connected", async () => {
|
|
279
|
+
const connector = new DiscordConnector({
|
|
280
|
+
agentConfig,
|
|
281
|
+
discordConfig,
|
|
282
|
+
botToken: "valid-token",
|
|
283
|
+
fleetManager,
|
|
284
|
+
sessionManager,
|
|
285
|
+
logger: mockLogger,
|
|
286
|
+
});
|
|
287
|
+
// First connect
|
|
288
|
+
const connectPromise = connector.connect();
|
|
289
|
+
await vi.waitFor(() => {
|
|
290
|
+
expect(connector.client).not.toBeNull();
|
|
291
|
+
});
|
|
292
|
+
const client = connector.client;
|
|
293
|
+
client.login.mockResolvedValue("token");
|
|
294
|
+
client.emit("ready", { user: client.user });
|
|
295
|
+
await connectPromise;
|
|
296
|
+
// Try to connect again
|
|
297
|
+
await expect(connector.connect()).rejects.toThrow(AlreadyConnectedError);
|
|
298
|
+
});
|
|
299
|
+
it("throws DiscordConnectionError on login failure", async () => {
|
|
300
|
+
// Set up login to fail BEFORE calling connect
|
|
301
|
+
setMockLoginBehavior("failure", "Invalid token");
|
|
302
|
+
const connector = new DiscordConnector({
|
|
303
|
+
agentConfig,
|
|
304
|
+
discordConfig,
|
|
305
|
+
botToken: "invalid-token",
|
|
306
|
+
fleetManager,
|
|
307
|
+
sessionManager,
|
|
308
|
+
logger: mockLogger,
|
|
309
|
+
});
|
|
310
|
+
await expect(connector.connect()).rejects.toThrow(DiscordConnectionError);
|
|
311
|
+
const state = connector.getState();
|
|
312
|
+
expect(state.status).toBe("error");
|
|
313
|
+
expect(state.lastError).toBe("Invalid token");
|
|
314
|
+
});
|
|
315
|
+
it("cleans up client on connection failure", async () => {
|
|
316
|
+
// Set up login to fail BEFORE calling connect
|
|
317
|
+
setMockLoginBehavior("failure", "Invalid token");
|
|
318
|
+
const connector = new DiscordConnector({
|
|
319
|
+
agentConfig,
|
|
320
|
+
discordConfig,
|
|
321
|
+
botToken: "invalid-token",
|
|
322
|
+
fleetManager,
|
|
323
|
+
sessionManager,
|
|
324
|
+
logger: mockLogger,
|
|
325
|
+
});
|
|
326
|
+
await expect(connector.connect()).rejects.toThrow(DiscordConnectionError);
|
|
327
|
+
expect(connector.client).toBeNull();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
// =============================================================================
|
|
331
|
+
// disconnect Tests
|
|
332
|
+
// =============================================================================
|
|
333
|
+
describe("disconnect", () => {
|
|
334
|
+
it("disconnects successfully", async () => {
|
|
335
|
+
const connector = new DiscordConnector({
|
|
336
|
+
agentConfig,
|
|
337
|
+
discordConfig,
|
|
338
|
+
botToken: "valid-token",
|
|
339
|
+
fleetManager,
|
|
340
|
+
sessionManager,
|
|
341
|
+
logger: mockLogger,
|
|
342
|
+
});
|
|
343
|
+
// Connect first
|
|
344
|
+
const connectPromise = connector.connect();
|
|
345
|
+
await vi.waitFor(() => {
|
|
346
|
+
expect(connector.client).not.toBeNull();
|
|
347
|
+
});
|
|
348
|
+
const client = connector.client;
|
|
349
|
+
client.login.mockResolvedValue("token");
|
|
350
|
+
client.emit("ready", { user: client.user });
|
|
351
|
+
await connectPromise;
|
|
352
|
+
// Now disconnect
|
|
353
|
+
await connector.disconnect();
|
|
354
|
+
expect(connector.isConnected()).toBe(false);
|
|
355
|
+
const state = connector.getState();
|
|
356
|
+
expect(state.status).toBe("disconnected");
|
|
357
|
+
expect(state.disconnectedAt).not.toBeNull();
|
|
358
|
+
expect(state.botUser).toBeNull();
|
|
359
|
+
});
|
|
360
|
+
it("handles disconnect when already disconnected", async () => {
|
|
361
|
+
const connector = new DiscordConnector({
|
|
362
|
+
agentConfig,
|
|
363
|
+
discordConfig,
|
|
364
|
+
botToken: "valid-token",
|
|
365
|
+
fleetManager,
|
|
366
|
+
sessionManager,
|
|
367
|
+
logger: mockLogger,
|
|
368
|
+
});
|
|
369
|
+
// Should not throw
|
|
370
|
+
await connector.disconnect();
|
|
371
|
+
expect(connector.isConnected()).toBe(false);
|
|
372
|
+
});
|
|
373
|
+
it("calls client.destroy() on disconnect", async () => {
|
|
374
|
+
const connector = new DiscordConnector({
|
|
375
|
+
agentConfig,
|
|
376
|
+
discordConfig,
|
|
377
|
+
botToken: "valid-token",
|
|
378
|
+
fleetManager,
|
|
379
|
+
sessionManager,
|
|
380
|
+
logger: mockLogger,
|
|
381
|
+
});
|
|
382
|
+
const connectPromise = connector.connect();
|
|
383
|
+
await vi.waitFor(() => {
|
|
384
|
+
expect(connector.client).not.toBeNull();
|
|
385
|
+
});
|
|
386
|
+
const client = connector.client;
|
|
387
|
+
client.login.mockResolvedValue("token");
|
|
388
|
+
client.emit("ready", { user: client.user });
|
|
389
|
+
await connectPromise;
|
|
390
|
+
await connector.disconnect();
|
|
391
|
+
expect(client.destroy).toHaveBeenCalled();
|
|
392
|
+
});
|
|
393
|
+
it("handles destroy error gracefully", async () => {
|
|
394
|
+
const connector = new DiscordConnector({
|
|
395
|
+
agentConfig,
|
|
396
|
+
discordConfig,
|
|
397
|
+
botToken: "valid-token",
|
|
398
|
+
fleetManager,
|
|
399
|
+
sessionManager,
|
|
400
|
+
logger: mockLogger,
|
|
401
|
+
});
|
|
402
|
+
const connectPromise = connector.connect();
|
|
403
|
+
await vi.waitFor(() => {
|
|
404
|
+
expect(connector.client).not.toBeNull();
|
|
405
|
+
});
|
|
406
|
+
const client = connector.client;
|
|
407
|
+
client.login.mockResolvedValue("token");
|
|
408
|
+
client.emit("ready", { user: client.user });
|
|
409
|
+
await connectPromise;
|
|
410
|
+
// Make destroy throw
|
|
411
|
+
client.destroy.mockImplementation(() => {
|
|
412
|
+
throw new Error("Destroy failed");
|
|
413
|
+
});
|
|
414
|
+
// Should not throw
|
|
415
|
+
await connector.disconnect();
|
|
416
|
+
expect(connector.isConnected()).toBe(false);
|
|
417
|
+
expect(mockLogger.error).toHaveBeenCalled();
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
// =============================================================================
|
|
421
|
+
// Connection Event Tests
|
|
422
|
+
// =============================================================================
|
|
423
|
+
describe("connection events", () => {
|
|
424
|
+
it("emits disconnect event on shard disconnect", async () => {
|
|
425
|
+
const connector = new DiscordConnector({
|
|
426
|
+
agentConfig,
|
|
427
|
+
discordConfig,
|
|
428
|
+
botToken: "valid-token",
|
|
429
|
+
fleetManager,
|
|
430
|
+
sessionManager,
|
|
431
|
+
logger: mockLogger,
|
|
432
|
+
});
|
|
433
|
+
const disconnectHandler = vi.fn();
|
|
434
|
+
connector.on("disconnect", disconnectHandler);
|
|
435
|
+
const connectPromise = connector.connect();
|
|
436
|
+
await vi.waitFor(() => {
|
|
437
|
+
expect(connector.client).not.toBeNull();
|
|
438
|
+
});
|
|
439
|
+
const client = connector.client;
|
|
440
|
+
client.login.mockResolvedValue("token");
|
|
441
|
+
client.emit("ready", { user: client.user });
|
|
442
|
+
await connectPromise;
|
|
443
|
+
// Simulate disconnect
|
|
444
|
+
client.emit("shardDisconnect", { code: 1001 });
|
|
445
|
+
expect(disconnectHandler).toHaveBeenCalledWith({
|
|
446
|
+
agentName: "test-agent",
|
|
447
|
+
code: 1001,
|
|
448
|
+
reason: "Shard disconnected",
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
it("emits reconnecting event on shard reconnecting", async () => {
|
|
452
|
+
const connector = new DiscordConnector({
|
|
453
|
+
agentConfig,
|
|
454
|
+
discordConfig,
|
|
455
|
+
botToken: "valid-token",
|
|
456
|
+
fleetManager,
|
|
457
|
+
sessionManager,
|
|
458
|
+
logger: mockLogger,
|
|
459
|
+
});
|
|
460
|
+
const reconnectingHandler = vi.fn();
|
|
461
|
+
connector.on("reconnecting", reconnectingHandler);
|
|
462
|
+
const connectPromise = connector.connect();
|
|
463
|
+
await vi.waitFor(() => {
|
|
464
|
+
expect(connector.client).not.toBeNull();
|
|
465
|
+
});
|
|
466
|
+
const client = connector.client;
|
|
467
|
+
client.login.mockResolvedValue("token");
|
|
468
|
+
client.emit("ready", { user: client.user });
|
|
469
|
+
await connectPromise;
|
|
470
|
+
// Simulate reconnecting
|
|
471
|
+
client.emit("shardReconnecting");
|
|
472
|
+
expect(reconnectingHandler).toHaveBeenCalledWith({
|
|
473
|
+
agentName: "test-agent",
|
|
474
|
+
attempt: 1,
|
|
475
|
+
});
|
|
476
|
+
const state = connector.getState();
|
|
477
|
+
expect(state.status).toBe("reconnecting");
|
|
478
|
+
expect(state.reconnectAttempts).toBe(1);
|
|
479
|
+
});
|
|
480
|
+
it("emits reconnected event on shard resume", async () => {
|
|
481
|
+
const connector = new DiscordConnector({
|
|
482
|
+
agentConfig,
|
|
483
|
+
discordConfig,
|
|
484
|
+
botToken: "valid-token",
|
|
485
|
+
fleetManager,
|
|
486
|
+
sessionManager,
|
|
487
|
+
logger: mockLogger,
|
|
488
|
+
});
|
|
489
|
+
const reconnectedHandler = vi.fn();
|
|
490
|
+
connector.on("reconnected", reconnectedHandler);
|
|
491
|
+
const connectPromise = connector.connect();
|
|
492
|
+
await vi.waitFor(() => {
|
|
493
|
+
expect(connector.client).not.toBeNull();
|
|
494
|
+
});
|
|
495
|
+
const client = connector.client;
|
|
496
|
+
client.login.mockResolvedValue("token");
|
|
497
|
+
client.emit("ready", { user: client.user });
|
|
498
|
+
await connectPromise;
|
|
499
|
+
// Simulate reconnect cycle
|
|
500
|
+
client.emit("shardReconnecting");
|
|
501
|
+
client.emit("shardResume");
|
|
502
|
+
expect(reconnectedHandler).toHaveBeenCalledWith({
|
|
503
|
+
agentName: "test-agent",
|
|
504
|
+
});
|
|
505
|
+
const state = connector.getState();
|
|
506
|
+
expect(state.status).toBe("connected");
|
|
507
|
+
});
|
|
508
|
+
it("emits error event on client error", async () => {
|
|
509
|
+
const connector = new DiscordConnector({
|
|
510
|
+
agentConfig,
|
|
511
|
+
discordConfig,
|
|
512
|
+
botToken: "valid-token",
|
|
513
|
+
fleetManager,
|
|
514
|
+
sessionManager,
|
|
515
|
+
logger: mockLogger,
|
|
516
|
+
});
|
|
517
|
+
const errorHandler = vi.fn();
|
|
518
|
+
connector.on("error", errorHandler);
|
|
519
|
+
const connectPromise = connector.connect();
|
|
520
|
+
await vi.waitFor(() => {
|
|
521
|
+
expect(connector.client).not.toBeNull();
|
|
522
|
+
});
|
|
523
|
+
const client = connector.client;
|
|
524
|
+
client.login.mockResolvedValue("token");
|
|
525
|
+
client.emit("ready", { user: client.user });
|
|
526
|
+
await connectPromise;
|
|
527
|
+
// Simulate error
|
|
528
|
+
const testError = new Error("Test error");
|
|
529
|
+
client.emit("error", testError);
|
|
530
|
+
expect(errorHandler).toHaveBeenCalledWith({
|
|
531
|
+
agentName: "test-agent",
|
|
532
|
+
error: testError,
|
|
533
|
+
});
|
|
534
|
+
const state = connector.getState();
|
|
535
|
+
expect(state.lastError).toBe("Test error");
|
|
536
|
+
});
|
|
537
|
+
it("tracks multiple reconnection attempts", async () => {
|
|
538
|
+
const connector = new DiscordConnector({
|
|
539
|
+
agentConfig,
|
|
540
|
+
discordConfig,
|
|
541
|
+
botToken: "valid-token",
|
|
542
|
+
fleetManager,
|
|
543
|
+
sessionManager,
|
|
544
|
+
logger: mockLogger,
|
|
545
|
+
});
|
|
546
|
+
const connectPromise = connector.connect();
|
|
547
|
+
await vi.waitFor(() => {
|
|
548
|
+
expect(connector.client).not.toBeNull();
|
|
549
|
+
});
|
|
550
|
+
const client = connector.client;
|
|
551
|
+
client.login.mockResolvedValue("token");
|
|
552
|
+
client.emit("ready", { user: client.user });
|
|
553
|
+
await connectPromise;
|
|
554
|
+
// Simulate multiple reconnection attempts
|
|
555
|
+
client.emit("shardReconnecting");
|
|
556
|
+
expect(connector.getState().reconnectAttempts).toBe(1);
|
|
557
|
+
client.emit("shardReconnecting");
|
|
558
|
+
expect(connector.getState().reconnectAttempts).toBe(2);
|
|
559
|
+
client.emit("shardReconnecting");
|
|
560
|
+
expect(connector.getState().reconnectAttempts).toBe(3);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
// =============================================================================
|
|
564
|
+
// Presence Tests
|
|
565
|
+
// =============================================================================
|
|
566
|
+
describe("presence", () => {
|
|
567
|
+
it("sets presence when configured", async () => {
|
|
568
|
+
const configWithPresence = {
|
|
569
|
+
...discordConfig,
|
|
570
|
+
presence: {
|
|
571
|
+
activity_type: "watching",
|
|
572
|
+
activity_message: "for support requests",
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
const connector = new DiscordConnector({
|
|
576
|
+
agentConfig,
|
|
577
|
+
discordConfig: configWithPresence,
|
|
578
|
+
botToken: "valid-token",
|
|
579
|
+
fleetManager,
|
|
580
|
+
sessionManager,
|
|
581
|
+
logger: mockLogger,
|
|
582
|
+
});
|
|
583
|
+
const connectPromise = connector.connect();
|
|
584
|
+
await vi.waitFor(() => {
|
|
585
|
+
expect(connector.client).not.toBeNull();
|
|
586
|
+
});
|
|
587
|
+
const client = connector.client;
|
|
588
|
+
client.login.mockResolvedValue("token");
|
|
589
|
+
client.emit("ready", { user: client.user });
|
|
590
|
+
await connectPromise;
|
|
591
|
+
expect(client.user.setActivity).toHaveBeenCalledWith("for support requests", {
|
|
592
|
+
type: 3, // watching
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
it("does not set presence when not configured", async () => {
|
|
596
|
+
const connector = new DiscordConnector({
|
|
597
|
+
agentConfig,
|
|
598
|
+
discordConfig, // No presence configured
|
|
599
|
+
botToken: "valid-token",
|
|
600
|
+
fleetManager,
|
|
601
|
+
sessionManager,
|
|
602
|
+
logger: mockLogger,
|
|
603
|
+
});
|
|
604
|
+
const connectPromise = connector.connect();
|
|
605
|
+
await vi.waitFor(() => {
|
|
606
|
+
expect(connector.client).not.toBeNull();
|
|
607
|
+
});
|
|
608
|
+
const client = connector.client;
|
|
609
|
+
client.login.mockResolvedValue("token");
|
|
610
|
+
client.emit("ready", { user: client.user });
|
|
611
|
+
await connectPromise;
|
|
612
|
+
expect(client.user.setActivity).not.toHaveBeenCalled();
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
// =============================================================================
|
|
616
|
+
// Rate Limit Tests
|
|
617
|
+
// =============================================================================
|
|
618
|
+
describe("rate limit handling", () => {
|
|
619
|
+
it("emits rateLimit event when rate limited", async () => {
|
|
620
|
+
const connector = new DiscordConnector({
|
|
621
|
+
agentConfig,
|
|
622
|
+
discordConfig,
|
|
623
|
+
botToken: "valid-token",
|
|
624
|
+
fleetManager,
|
|
625
|
+
sessionManager,
|
|
626
|
+
logger: mockLogger,
|
|
627
|
+
});
|
|
628
|
+
const rateLimitHandler = vi.fn();
|
|
629
|
+
connector.on("rateLimit", rateLimitHandler);
|
|
630
|
+
const connectPromise = connector.connect();
|
|
631
|
+
await vi.waitFor(() => {
|
|
632
|
+
expect(connector.client).not.toBeNull();
|
|
633
|
+
});
|
|
634
|
+
const client = connector.client;
|
|
635
|
+
client.login.mockResolvedValue("token");
|
|
636
|
+
client.emit("ready", { user: client.user });
|
|
637
|
+
await connectPromise;
|
|
638
|
+
// Simulate rate limit event
|
|
639
|
+
client.rest.emit("rateLimited", {
|
|
640
|
+
timeToReset: 5000,
|
|
641
|
+
limit: 50,
|
|
642
|
+
method: "POST",
|
|
643
|
+
hash: "abc123",
|
|
644
|
+
route: "/channels/123/messages",
|
|
645
|
+
global: false,
|
|
646
|
+
});
|
|
647
|
+
expect(rateLimitHandler).toHaveBeenCalledWith({
|
|
648
|
+
agentName: "test-agent",
|
|
649
|
+
timeToReset: 5000,
|
|
650
|
+
limit: 50,
|
|
651
|
+
method: "POST",
|
|
652
|
+
hash: "abc123",
|
|
653
|
+
route: "/channels/123/messages",
|
|
654
|
+
global: false,
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
it("logs rate limit at info level", async () => {
|
|
658
|
+
const connector = new DiscordConnector({
|
|
659
|
+
agentConfig,
|
|
660
|
+
discordConfig,
|
|
661
|
+
botToken: "valid-token",
|
|
662
|
+
fleetManager,
|
|
663
|
+
sessionManager,
|
|
664
|
+
logger: mockLogger,
|
|
665
|
+
});
|
|
666
|
+
const connectPromise = connector.connect();
|
|
667
|
+
await vi.waitFor(() => {
|
|
668
|
+
expect(connector.client).not.toBeNull();
|
|
669
|
+
});
|
|
670
|
+
const client = connector.client;
|
|
671
|
+
client.login.mockResolvedValue("token");
|
|
672
|
+
client.emit("ready", { user: client.user });
|
|
673
|
+
await connectPromise;
|
|
674
|
+
// Simulate rate limit event
|
|
675
|
+
client.rest.emit("rateLimited", {
|
|
676
|
+
timeToReset: 5000,
|
|
677
|
+
limit: 50,
|
|
678
|
+
method: "POST",
|
|
679
|
+
hash: "abc123",
|
|
680
|
+
route: "/channels/123/messages",
|
|
681
|
+
global: false,
|
|
682
|
+
});
|
|
683
|
+
expect(mockLogger.info).toHaveBeenCalledWith("Rate limited by Discord API", expect.objectContaining({
|
|
684
|
+
route: "/channels/123/messages",
|
|
685
|
+
method: "POST",
|
|
686
|
+
timeToReset: 5000,
|
|
687
|
+
limit: 50,
|
|
688
|
+
global: false,
|
|
689
|
+
hash: "abc123",
|
|
690
|
+
}));
|
|
691
|
+
});
|
|
692
|
+
it("tracks rate limit count in state", async () => {
|
|
693
|
+
const connector = new DiscordConnector({
|
|
694
|
+
agentConfig,
|
|
695
|
+
discordConfig,
|
|
696
|
+
botToken: "valid-token",
|
|
697
|
+
fleetManager,
|
|
698
|
+
sessionManager,
|
|
699
|
+
logger: mockLogger,
|
|
700
|
+
});
|
|
701
|
+
const connectPromise = connector.connect();
|
|
702
|
+
await vi.waitFor(() => {
|
|
703
|
+
expect(connector.client).not.toBeNull();
|
|
704
|
+
});
|
|
705
|
+
const client = connector.client;
|
|
706
|
+
client.login.mockResolvedValue("token");
|
|
707
|
+
client.emit("ready", { user: client.user });
|
|
708
|
+
await connectPromise;
|
|
709
|
+
// Initial state should have no rate limits
|
|
710
|
+
let state = connector.getState();
|
|
711
|
+
expect(state.rateLimits.totalCount).toBe(0);
|
|
712
|
+
expect(state.rateLimits.lastRateLimitAt).toBeNull();
|
|
713
|
+
expect(state.rateLimits.isRateLimited).toBe(false);
|
|
714
|
+
// Simulate first rate limit
|
|
715
|
+
client.rest.emit("rateLimited", {
|
|
716
|
+
timeToReset: 5000,
|
|
717
|
+
limit: 50,
|
|
718
|
+
method: "POST",
|
|
719
|
+
hash: "abc123",
|
|
720
|
+
route: "/channels/123/messages",
|
|
721
|
+
global: false,
|
|
722
|
+
});
|
|
723
|
+
state = connector.getState();
|
|
724
|
+
expect(state.rateLimits.totalCount).toBe(1);
|
|
725
|
+
expect(state.rateLimits.lastRateLimitAt).not.toBeNull();
|
|
726
|
+
expect(state.rateLimits.isRateLimited).toBe(true);
|
|
727
|
+
expect(state.rateLimits.currentResetTime).toBe(5000);
|
|
728
|
+
// Simulate second rate limit
|
|
729
|
+
client.rest.emit("rateLimited", {
|
|
730
|
+
timeToReset: 3000,
|
|
731
|
+
limit: 50,
|
|
732
|
+
method: "GET",
|
|
733
|
+
hash: "def456",
|
|
734
|
+
route: "/guilds/123",
|
|
735
|
+
global: false,
|
|
736
|
+
});
|
|
737
|
+
state = connector.getState();
|
|
738
|
+
expect(state.rateLimits.totalCount).toBe(2);
|
|
739
|
+
expect(state.rateLimits.currentResetTime).toBe(3000);
|
|
740
|
+
});
|
|
741
|
+
it("clears rate limit status after reset time", async () => {
|
|
742
|
+
vi.useFakeTimers();
|
|
743
|
+
const connector = new DiscordConnector({
|
|
744
|
+
agentConfig,
|
|
745
|
+
discordConfig,
|
|
746
|
+
botToken: "valid-token",
|
|
747
|
+
fleetManager,
|
|
748
|
+
sessionManager,
|
|
749
|
+
logger: mockLogger,
|
|
750
|
+
});
|
|
751
|
+
const connectPromise = connector.connect();
|
|
752
|
+
await vi.waitFor(() => {
|
|
753
|
+
expect(connector.client).not.toBeNull();
|
|
754
|
+
});
|
|
755
|
+
const client = connector.client;
|
|
756
|
+
client.login.mockResolvedValue("token");
|
|
757
|
+
client.emit("ready", { user: client.user });
|
|
758
|
+
await connectPromise;
|
|
759
|
+
// Simulate rate limit with 5 second reset
|
|
760
|
+
client.rest.emit("rateLimited", {
|
|
761
|
+
timeToReset: 5000,
|
|
762
|
+
limit: 50,
|
|
763
|
+
method: "POST",
|
|
764
|
+
hash: "abc123",
|
|
765
|
+
route: "/channels/123/messages",
|
|
766
|
+
global: false,
|
|
767
|
+
});
|
|
768
|
+
let state = connector.getState();
|
|
769
|
+
expect(state.rateLimits.isRateLimited).toBe(true);
|
|
770
|
+
// Advance time past the reset
|
|
771
|
+
vi.advanceTimersByTime(5001);
|
|
772
|
+
state = connector.getState();
|
|
773
|
+
expect(state.rateLimits.isRateLimited).toBe(false);
|
|
774
|
+
expect(state.rateLimits.currentResetTime).toBe(0);
|
|
775
|
+
// Total count should still be 1
|
|
776
|
+
expect(state.rateLimits.totalCount).toBe(1);
|
|
777
|
+
vi.useRealTimers();
|
|
778
|
+
});
|
|
779
|
+
it("handles global rate limits", async () => {
|
|
780
|
+
const connector = new DiscordConnector({
|
|
781
|
+
agentConfig,
|
|
782
|
+
discordConfig,
|
|
783
|
+
botToken: "valid-token",
|
|
784
|
+
fleetManager,
|
|
785
|
+
sessionManager,
|
|
786
|
+
logger: mockLogger,
|
|
787
|
+
});
|
|
788
|
+
const rateLimitHandler = vi.fn();
|
|
789
|
+
connector.on("rateLimit", rateLimitHandler);
|
|
790
|
+
const connectPromise = connector.connect();
|
|
791
|
+
await vi.waitFor(() => {
|
|
792
|
+
expect(connector.client).not.toBeNull();
|
|
793
|
+
});
|
|
794
|
+
const client = connector.client;
|
|
795
|
+
client.login.mockResolvedValue("token");
|
|
796
|
+
client.emit("ready", { user: client.user });
|
|
797
|
+
await connectPromise;
|
|
798
|
+
// Simulate global rate limit
|
|
799
|
+
client.rest.emit("rateLimited", {
|
|
800
|
+
timeToReset: 60000,
|
|
801
|
+
limit: 50,
|
|
802
|
+
method: "POST",
|
|
803
|
+
hash: "global",
|
|
804
|
+
route: "/channels/123/messages",
|
|
805
|
+
global: true,
|
|
806
|
+
});
|
|
807
|
+
expect(rateLimitHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
808
|
+
global: true,
|
|
809
|
+
}));
|
|
810
|
+
});
|
|
811
|
+
it("clears rate limit timer on disconnect", async () => {
|
|
812
|
+
vi.useFakeTimers();
|
|
813
|
+
const connector = new DiscordConnector({
|
|
814
|
+
agentConfig,
|
|
815
|
+
discordConfig,
|
|
816
|
+
botToken: "valid-token",
|
|
817
|
+
fleetManager,
|
|
818
|
+
sessionManager,
|
|
819
|
+
logger: mockLogger,
|
|
820
|
+
});
|
|
821
|
+
const connectPromise = connector.connect();
|
|
822
|
+
await vi.waitFor(() => {
|
|
823
|
+
expect(connector.client).not.toBeNull();
|
|
824
|
+
});
|
|
825
|
+
const client = connector.client;
|
|
826
|
+
client.login.mockResolvedValue("token");
|
|
827
|
+
client.emit("ready", { user: client.user });
|
|
828
|
+
await connectPromise;
|
|
829
|
+
// Simulate rate limit
|
|
830
|
+
client.rest.emit("rateLimited", {
|
|
831
|
+
timeToReset: 5000,
|
|
832
|
+
limit: 50,
|
|
833
|
+
method: "POST",
|
|
834
|
+
hash: "abc123",
|
|
835
|
+
route: "/channels/123/messages",
|
|
836
|
+
global: false,
|
|
837
|
+
});
|
|
838
|
+
let state = connector.getState();
|
|
839
|
+
expect(state.rateLimits.isRateLimited).toBe(true);
|
|
840
|
+
// Disconnect before timer expires
|
|
841
|
+
await connector.disconnect();
|
|
842
|
+
state = connector.getState();
|
|
843
|
+
expect(state.rateLimits.isRateLimited).toBe(false);
|
|
844
|
+
expect(state.rateLimits.currentResetTime).toBe(0);
|
|
845
|
+
vi.useRealTimers();
|
|
846
|
+
});
|
|
847
|
+
it("returns initial rate limit state before connection", () => {
|
|
848
|
+
const connector = new DiscordConnector({
|
|
849
|
+
agentConfig,
|
|
850
|
+
discordConfig,
|
|
851
|
+
botToken: "valid-token",
|
|
852
|
+
fleetManager,
|
|
853
|
+
sessionManager,
|
|
854
|
+
logger: mockLogger,
|
|
855
|
+
});
|
|
856
|
+
const state = connector.getState();
|
|
857
|
+
expect(state.rateLimits).toEqual({
|
|
858
|
+
totalCount: 0,
|
|
859
|
+
lastRateLimitAt: null,
|
|
860
|
+
isRateLimited: false,
|
|
861
|
+
currentResetTime: 0,
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
// =============================================================================
|
|
866
|
+
// Message Stats Tests
|
|
867
|
+
// =============================================================================
|
|
868
|
+
describe("message stats", () => {
|
|
869
|
+
it("returns initial message stats before connection", () => {
|
|
870
|
+
const connector = new DiscordConnector({
|
|
871
|
+
agentConfig,
|
|
872
|
+
discordConfig,
|
|
873
|
+
botToken: "valid-token",
|
|
874
|
+
fleetManager,
|
|
875
|
+
sessionManager,
|
|
876
|
+
logger: mockLogger,
|
|
877
|
+
});
|
|
878
|
+
const state = connector.getState();
|
|
879
|
+
expect(state.messageStats).toEqual({
|
|
880
|
+
received: 0,
|
|
881
|
+
sent: 0,
|
|
882
|
+
ignored: 0,
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
// =============================================================================
|
|
887
|
+
// isConnected Tests
|
|
888
|
+
// =============================================================================
|
|
889
|
+
describe("isConnected", () => {
|
|
890
|
+
it("returns false when disconnected", () => {
|
|
891
|
+
const connector = new DiscordConnector({
|
|
892
|
+
agentConfig,
|
|
893
|
+
discordConfig,
|
|
894
|
+
botToken: "valid-token",
|
|
895
|
+
fleetManager,
|
|
896
|
+
sessionManager,
|
|
897
|
+
logger: mockLogger,
|
|
898
|
+
});
|
|
899
|
+
expect(connector.isConnected()).toBe(false);
|
|
900
|
+
});
|
|
901
|
+
it("returns true when connected", async () => {
|
|
902
|
+
const connector = new DiscordConnector({
|
|
903
|
+
agentConfig,
|
|
904
|
+
discordConfig,
|
|
905
|
+
botToken: "valid-token",
|
|
906
|
+
fleetManager,
|
|
907
|
+
sessionManager,
|
|
908
|
+
logger: mockLogger,
|
|
909
|
+
});
|
|
910
|
+
const connectPromise = connector.connect();
|
|
911
|
+
await vi.waitFor(() => {
|
|
912
|
+
expect(connector.client).not.toBeNull();
|
|
913
|
+
});
|
|
914
|
+
const client = connector.client;
|
|
915
|
+
client.login.mockResolvedValue("token");
|
|
916
|
+
client.emit("ready", { user: client.user });
|
|
917
|
+
await connectPromise;
|
|
918
|
+
expect(connector.isConnected()).toBe(true);
|
|
919
|
+
});
|
|
920
|
+
it("returns false when reconnecting", async () => {
|
|
921
|
+
const connector = new DiscordConnector({
|
|
922
|
+
agentConfig,
|
|
923
|
+
discordConfig,
|
|
924
|
+
botToken: "valid-token",
|
|
925
|
+
fleetManager,
|
|
926
|
+
sessionManager,
|
|
927
|
+
logger: mockLogger,
|
|
928
|
+
});
|
|
929
|
+
const connectPromise = connector.connect();
|
|
930
|
+
await vi.waitFor(() => {
|
|
931
|
+
expect(connector.client).not.toBeNull();
|
|
932
|
+
});
|
|
933
|
+
const client = connector.client;
|
|
934
|
+
client.login.mockResolvedValue("token");
|
|
935
|
+
client.emit("ready", { user: client.user });
|
|
936
|
+
await connectPromise;
|
|
937
|
+
client.emit("shardReconnecting");
|
|
938
|
+
expect(connector.isConnected()).toBe(false);
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
// =============================================================================
|
|
942
|
+
// agentName Tests
|
|
943
|
+
// =============================================================================
|
|
944
|
+
describe("agentName", () => {
|
|
945
|
+
it("returns the agent name from config", () => {
|
|
946
|
+
const connector = new DiscordConnector({
|
|
947
|
+
agentConfig,
|
|
948
|
+
discordConfig,
|
|
949
|
+
botToken: "valid-token",
|
|
950
|
+
fleetManager,
|
|
951
|
+
sessionManager,
|
|
952
|
+
logger: mockLogger,
|
|
953
|
+
});
|
|
954
|
+
expect(connector.agentName).toBe("test-agent");
|
|
955
|
+
});
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
//# sourceMappingURL=discord-connector.test.js.map
|