Skip to content

Commit 1000dd6

Browse files
authored
Merge pull request #2404 from ai16z-demirix/test/client-slack
feat: adding tests for slack client. Moving existing tests to new __tests__ directory.
2 parents 5ae8feb + 9f8f0dc commit 1000dd6

8 files changed

+313
-417
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { MessageManager } from '../src/messages';
3+
import { WebClient } from '@slack/web-api';
4+
import { IAgentRuntime } from '@elizaos/core';
5+
6+
// Mock dependencies
7+
vi.mock('@slack/web-api');
8+
vi.mock('@elizaos/core');
9+
10+
describe('MessageManager', () => {
11+
let mockWebClient: WebClient;
12+
let mockRuntime: IAgentRuntime;
13+
let messageManager: MessageManager;
14+
const mockBotUserId = 'U123456';
15+
16+
beforeEach(() => {
17+
// Setup mock WebClient
18+
mockWebClient = {
19+
chat: {
20+
postMessage: vi.fn()
21+
}
22+
} as unknown as WebClient;
23+
24+
// Setup mock runtime
25+
mockRuntime = {
26+
getSetting: vi.fn(),
27+
character: {
28+
name: 'TestBot'
29+
}
30+
} as unknown as IAgentRuntime;
31+
32+
messageManager = new MessageManager(mockWebClient, mockRuntime, mockBotUserId);
33+
});
34+
35+
it('should initialize with correct parameters', () => {
36+
expect(messageManager).toBeDefined();
37+
});
38+
39+
it('should not process duplicate events', () => {
40+
const eventId = 'evt_123';
41+
const result1 = messageManager['processedEvents'].has(eventId);
42+
expect(result1).toBe(false);
43+
44+
// Add event to processed set
45+
messageManager['processedEvents'].add(eventId);
46+
const result2 = messageManager['processedEvents'].has(eventId);
47+
expect(result2).toBe(true);
48+
});
49+
50+
it('should handle message processing lock correctly', () => {
51+
const messageId = 'msg_123';
52+
const isLocked1 = messageManager['messageProcessingLock'].has(messageId);
53+
expect(isLocked1).toBe(false);
54+
55+
// Lock message
56+
messageManager['messageProcessingLock'].add(messageId);
57+
const isLocked2 = messageManager['messageProcessingLock'].has(messageId);
58+
expect(isLocked2).toBe(true);
59+
});
60+
61+
it('should clean up old processed messages', () => {
62+
vi.useFakeTimers();
63+
const oldMessageId = 'old_msg';
64+
const newMessageId = 'new_msg';
65+
66+
// Add messages with different timestamps
67+
messageManager['processedMessages'].set(oldMessageId, Date.now() - 3700000); // older than 1 hour
68+
messageManager['processedMessages'].set(newMessageId, Date.now()); // current
69+
70+
// Trigger cleanup by advancing time and running interval callback
71+
const cleanupInterval = setInterval(() => {
72+
const oneHourAgo = Date.now() - 3600000;
73+
for (const [key, timestamp] of messageManager['processedMessages'].entries()) {
74+
if (timestamp < oneHourAgo) {
75+
messageManager['processedMessages'].delete(key);
76+
}
77+
}
78+
}, 3600000);
79+
80+
vi.advanceTimersByTime(3600000);
81+
82+
// Check if old message was cleaned up
83+
expect(messageManager['processedMessages'].has(oldMessageId)).toBe(false);
84+
expect(messageManager['processedMessages'].has(newMessageId)).toBe(true);
85+
86+
clearInterval(cleanupInterval);
87+
vi.useRealTimers();
88+
});
89+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { describe, expect, test, beforeEach, vi } from "vitest";
2+
import { SlackClientProvider } from "../src/providers/slack-client.provider";
3+
import { SlackConfig } from "../src/types/slack-types";
4+
import { WebClient } from "@slack/web-api";
5+
import type {
6+
AuthTestResponse,
7+
ChatPostMessageResponse,
8+
} from "@slack/web-api";
9+
10+
vi.mock("@slack/web-api");
11+
12+
// Mock setup functions
13+
const createMockSlackResponse = (ok: boolean, additionalData = {}) => ({
14+
ok,
15+
...additionalData,
16+
});
17+
18+
const getMockWebClient = () => {
19+
return {
20+
auth: {
21+
test: vi.fn(),
22+
},
23+
chat: {
24+
postMessage: vi.fn(),
25+
},
26+
} as unknown as WebClient;
27+
};
28+
29+
describe("SlackClientProvider", () => {
30+
let provider: SlackClientProvider;
31+
let mockWebClient: WebClient;
32+
let mockConfig: SlackConfig;
33+
34+
beforeEach(() => {
35+
vi.clearAllMocks();
36+
mockConfig = {
37+
appId: "test-app-id",
38+
clientId: "test-client-id",
39+
clientSecret: "test-client-secret",
40+
signingSecret: "test-signing-secret",
41+
verificationToken: "test-verification-token",
42+
botToken: "test-bot-token",
43+
botId: "test-bot-id",
44+
};
45+
mockWebClient = getMockWebClient();
46+
provider = new SlackClientProvider(mockConfig);
47+
// @ts-ignore - setting mock client for testing
48+
provider['client'] = mockWebClient;
49+
});
50+
51+
describe("Initialization", () => {
52+
test("should create a provider instance with default retry options", () => {
53+
expect(provider).toBeInstanceOf(SlackClientProvider);
54+
const context = provider.getContext();
55+
expect(context).toHaveProperty("client");
56+
expect(context).toHaveProperty("config");
57+
expect(context.config).toEqual(mockConfig);
58+
});
59+
60+
test("should create a provider instance with custom retry options", () => {
61+
const retryOptions = {
62+
maxRetries: 5,
63+
initialDelay: 2000,
64+
maxDelay: 10000,
65+
};
66+
const providerWithOptions = new SlackClientProvider(mockConfig, retryOptions);
67+
// @ts-ignore - setting mock client for testing
68+
providerWithOptions['client'] = mockWebClient;
69+
70+
expect(providerWithOptions).toBeInstanceOf(SlackClientProvider);
71+
const context = providerWithOptions.getContext();
72+
expect(context).toHaveProperty("client");
73+
expect(context).toHaveProperty("config");
74+
expect(context.config).toEqual(mockConfig);
75+
});
76+
});
77+
78+
describe("Connection Validation", () => {
79+
test("should validate connection successfully", async () => {
80+
const mockResponse = createMockSlackResponse(true, {
81+
user_id: "test-bot-id",
82+
}) as AuthTestResponse;
83+
const mockTest = mockWebClient.auth.test as vi.Mock;
84+
mockTest.mockResolvedValue(mockResponse);
85+
86+
const result = await provider.validateConnection();
87+
expect(result).toBe(true);
88+
});
89+
90+
test("should handle failed validation", async () => {
91+
const mockResponse = createMockSlackResponse(false) as AuthTestResponse;
92+
const mockTest = mockWebClient.auth.test as vi.Mock;
93+
mockTest.mockResolvedValue(mockResponse);
94+
95+
const result = await provider.validateConnection();
96+
expect(result).toBe(false);
97+
});
98+
99+
test("should handle connection errors", async () => {
100+
const mockTest = mockWebClient.auth.test as vi.Mock;
101+
mockTest.mockRejectedValue(new Error("Connection failed"));
102+
103+
const result = await provider.validateConnection();
104+
expect(result).toBe(false);
105+
});
106+
});
107+
108+
describe("Message Sending", () => {
109+
const channelId = "test-channel";
110+
const text = "Hello, world!";
111+
112+
test("should successfully send a message", async () => {
113+
const expectedResponse = createMockSlackResponse(true, {
114+
ts: "1234567890.123456",
115+
}) as ChatPostMessageResponse;
116+
const mockPostMessage = mockWebClient.chat.postMessage as vi.Mock;
117+
mockPostMessage.mockResolvedValue(expectedResponse);
118+
119+
const result = await provider.sendMessage(channelId, text);
120+
expect(result.ok).toBe(true);
121+
expect(mockPostMessage).toHaveBeenCalledWith({
122+
channel: channelId,
123+
text: text,
124+
});
125+
});
126+
127+
test("should handle rate limiting", async () => {
128+
const mockResponse = createMockSlackResponse(true) as ChatPostMessageResponse;
129+
const mockPostMessage = mockWebClient.chat.postMessage as vi.Mock;
130+
131+
mockPostMessage
132+
.mockRejectedValueOnce(new Error("rate_limited"))
133+
.mockResolvedValueOnce(mockResponse);
134+
135+
const result = await provider.sendMessage(channelId, text);
136+
expect(result.ok).toBe(true);
137+
});
138+
139+
test("should handle network errors with retry", async () => {
140+
const mockResponse = createMockSlackResponse(true) as ChatPostMessageResponse;
141+
const mockPostMessage = mockWebClient.chat.postMessage as vi.Mock;
142+
143+
mockPostMessage
144+
.mockRejectedValueOnce(new Error("network_error"))
145+
.mockResolvedValueOnce(mockResponse);
146+
147+
const result = await provider.sendMessage(channelId, text);
148+
expect(result.ok).toBe(true);
149+
});
150+
});
151+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { SlackClient } from '../src/index';
3+
import { WebClient } from '@slack/web-api';
4+
import { IAgentRuntime, Character } from '@elizaos/core';
5+
6+
// Mock dependencies
7+
vi.mock('@slack/web-api');
8+
vi.mock('@elizaos/core');
9+
10+
describe('SlackClient', () => {
11+
let mockRuntime: IAgentRuntime;
12+
let slackClient: SlackClient;
13+
14+
beforeEach(() => {
15+
// Setup mock runtime
16+
mockRuntime = {
17+
getSetting: vi.fn((key: string) => {
18+
const settings: { [key: string]: string } = {
19+
'SLACK_BOT_TOKEN': 'test-token',
20+
'SLACK_SIGNING_SECRET': 'test-secret'
21+
};
22+
return settings[key];
23+
}),
24+
character: {} as Character
25+
} as unknown as IAgentRuntime;
26+
});
27+
28+
it('should initialize with correct settings', () => {
29+
slackClient = new SlackClient(mockRuntime);
30+
expect(mockRuntime.getSetting).toHaveBeenCalledWith('SLACK_BOT_TOKEN');
31+
expect(mockRuntime.getSetting).toHaveBeenCalledWith('SLACK_SIGNING_SECRET');
32+
});
33+
34+
it('should throw error if SLACK_BOT_TOKEN is missing', () => {
35+
mockRuntime.getSetting = vi.fn((key: string) => {
36+
const settings: { [key: string]: string } = {
37+
'SLACK_SIGNING_SECRET': 'test-secret'
38+
};
39+
return settings[key];
40+
});
41+
42+
expect(() => new SlackClient(mockRuntime)).toThrow('SLACK_BOT_TOKEN is required');
43+
});
44+
45+
it('should throw error if SLACK_SIGNING_SECRET is missing', () => {
46+
mockRuntime.getSetting = vi.fn((key: string) => {
47+
const settings: { [key: string]: string } = {
48+
'SLACK_BOT_TOKEN': 'test-token'
49+
};
50+
return settings[key];
51+
});
52+
53+
expect(() => new SlackClient(mockRuntime)).toThrow('SLACK_SIGNING_SECRET is required');
54+
});
55+
});

packages/client-slack/package.json

+5-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
],
2222
"scripts": {
2323
"build": "tsup src/index.ts --format esm --dts",
24-
"test": "jest",
24+
"test": "vitest run",
25+
"test:watch": "vitest",
2526
"lint": "eslint --fix --cache .",
2627
"clean": "rimraf dist",
2728
"dev": "tsup src/index.ts --watch",
@@ -44,14 +45,11 @@
4445
"devDependencies": {
4546
"@types/express": "^4.17.21",
4647
"@types/fluent-ffmpeg": "^2.1.24",
47-
"@types/jest": "^29.5.0",
4848
"@types/node": "^18.15.11",
49-
"jest": "^29.5.0",
5049
"rimraf": "^5.0.0",
51-
"ts-jest": "^29.1.0",
52-
"ts-node": "^10.9.1",
53-
"tsup": "^8.3.5",
54-
"typescript": "^5.0.0"
50+
"tsup": "^6.7.0",
51+
"typescript": "^5.0.3",
52+
"vitest": "^1.2.1"
5553
},
5654
"engines": {
5755
"node": ">=14.0.0"

0 commit comments

Comments
 (0)