Skip to content

Commit c791786

Browse files
authored
Merge pull request elizaOS#1976 from ai16z-demirix/test/plugin-twitter
test: adding tests for twitter plugin
2 parents 49d76c3 + d0854e8 commit c791786

File tree

3 files changed

+224
-1
lines changed

3 files changed

+224
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2+
import { postAction } from '../src/actions/post';
3+
import { ModelClass, IAgentRuntime, Memory, State, generateObject } from '@elizaos/core';
4+
import { TweetContent, TweetSchema } from '../src/types';
5+
import { tweetTemplate } from '../src/templates';
6+
import { UUID } from '../../core/src/types';
7+
8+
// Mock @elizaos/core
9+
vi.mock('@elizaos/core', async () => {
10+
const actual = await vi.importActual('@elizaos/core');
11+
return {
12+
...actual,
13+
generateObject: vi.fn().mockImplementation(async ({ schema }) => {
14+
if (schema === TweetSchema) {
15+
return {
16+
object: {
17+
text: 'Test tweet content'
18+
},
19+
raw: 'Test tweet content'
20+
};
21+
}
22+
return null;
23+
}),
24+
composeContext: vi.fn().mockImplementation(({ state, template }) => {
25+
// Return a properly formatted context that matches the template format
26+
return {
27+
state: {
28+
...state,
29+
recentMessages: state?.recentMessages || [],
30+
topics: state?.topics || [],
31+
postDirections: state?.postDirections || '',
32+
agentName: state?.agentName || 'TestAgent',
33+
},
34+
template,
35+
result: template.replace(/{{(\w+)}}/g, (_, key) => state?.[key] || key)
36+
};
37+
}),
38+
formatMessages: vi.fn().mockImplementation((messages) => messages),
39+
elizaLogger: {
40+
log: vi.fn(),
41+
error: vi.fn(),
42+
warn: vi.fn(),
43+
info: vi.fn(),
44+
},
45+
ModelClass: actual.ModelClass
46+
};
47+
});
48+
49+
// Create mock Scraper class
50+
const mockScraper = {
51+
login: vi.fn().mockResolvedValue(true),
52+
isLoggedIn: vi.fn().mockResolvedValue(true),
53+
sendTweet: vi.fn().mockResolvedValue({
54+
json: () => Promise.resolve({
55+
data: {
56+
create_tweet: {
57+
tweet_results: {
58+
result: {
59+
id: '123',
60+
text: 'Test tweet content'
61+
}
62+
}
63+
}
64+
}
65+
})
66+
}),
67+
};
68+
69+
// Mock the agent-twitter-client
70+
vi.mock('agent-twitter-client', () => ({
71+
Scraper: vi.fn().mockImplementation(() => mockScraper)
72+
}));
73+
74+
// Mock environment variables
75+
const originalEnv = process.env;
76+
beforeEach(() => {
77+
vi.resetModules();
78+
process.env = {
79+
...originalEnv,
80+
TWITTER_USERNAME: 'test_user',
81+
TWITTER_PASSWORD: 'test_pass',
82+
TWITTER_EMAIL: 'test@example.com',
83+
TWITTER_DRY_RUN: 'true'
84+
};
85+
86+
// Reset mock implementations
87+
mockScraper.login.mockResolvedValue(true);
88+
mockScraper.isLoggedIn.mockResolvedValue(true);
89+
mockScraper.sendTweet.mockResolvedValue({
90+
json: () => Promise.resolve({
91+
data: {
92+
create_tweet: {
93+
tweet_results: {
94+
result: {
95+
id: '123',
96+
text: 'Test tweet content'
97+
}
98+
}
99+
}
100+
}
101+
})
102+
});
103+
});
104+
105+
afterEach(() => {
106+
process.env = originalEnv;
107+
vi.clearAllMocks();
108+
});
109+
110+
describe('Twitter Post Action', () => {
111+
const mockRuntime: IAgentRuntime = {
112+
generateObject: vi.fn().mockImplementation(async ({ schema }) => {
113+
if (schema === TweetSchema) {
114+
return {
115+
object: {
116+
text: 'Test tweet content'
117+
},
118+
raw: 'Test tweet content'
119+
};
120+
}
121+
return null;
122+
}),
123+
getMemory: vi.fn(),
124+
getState: vi.fn(),
125+
setState: vi.fn(),
126+
getPlugin: vi.fn(),
127+
getPlugins: vi.fn(),
128+
getAction: vi.fn(),
129+
getActions: vi.fn(),
130+
getModel: vi.fn(),
131+
getModels: vi.fn(),
132+
getEmbedding: vi.fn(),
133+
getEmbeddings: vi.fn(),
134+
getTemplate: vi.fn(),
135+
getTemplates: vi.fn(),
136+
getCharacter: vi.fn(),
137+
getCharacters: vi.fn(),
138+
getPrompt: vi.fn(),
139+
getPrompts: vi.fn(),
140+
getPromptTemplate: vi.fn(),
141+
getPromptTemplates: vi.fn(),
142+
getPromptModel: vi.fn(),
143+
getPromptModels: vi.fn(),
144+
};
145+
146+
const mockMessage: Memory = {
147+
id: '123' as UUID,
148+
content: { text: 'Please tweet something' },
149+
userId: '123' as UUID,
150+
agentId: '123' as UUID,
151+
roomId: '123' as UUID
152+
};
153+
154+
const mockState: State = {
155+
topics: ['test topic'],
156+
recentMessages: "test",
157+
recentPostInteractions: [],
158+
postDirections: 'Be friendly',
159+
agentName: 'TestAgent',
160+
bio: '',
161+
lore: '',
162+
messageDirections: '',
163+
roomId: 'ads' as UUID,
164+
actors: '',
165+
recentMessagesData: []
166+
};
167+
168+
describe('validate', () => {
169+
it('should validate valid message content', async () => {
170+
const result = await postAction.validate(
171+
mockRuntime,
172+
mockMessage,
173+
mockState
174+
);
175+
expect(result).toBe(true);
176+
});
177+
178+
it('should fail validation without credentials', async () => {
179+
delete process.env.TWITTER_USERNAME;
180+
delete process.env.TWITTER_PASSWORD;
181+
182+
const result = await postAction.validate(
183+
mockRuntime,
184+
mockMessage,
185+
mockState
186+
);
187+
expect(result).toBe(false);
188+
});
189+
});
190+
191+
describe('handler', () => {
192+
it('should handle API errors', async () => {
193+
process.env.TWITTER_DRY_RUN = 'false';
194+
mockScraper.login.mockRejectedValueOnce(new Error('API Error'));
195+
mockScraper.isLoggedIn.mockResolvedValueOnce(false);
196+
197+
const result = await postAction.handler(
198+
mockRuntime,
199+
mockMessage,
200+
mockState
201+
);
202+
expect(result).toBe(false);
203+
});
204+
});
205+
});

packages/plugin-twitter/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
"agent-twitter-client": "0.0.18",
2424
"tsup": "8.3.5"
2525
},
26+
"devDependencies": {
27+
"vitest": "^1.0.0"
28+
},
2629
"scripts": {
2730
"build": "tsup --format esm --dts",
2831
"dev": "tsup --format esm --dts --watch",
29-
"test": "vitest run"
32+
"test": "vitest run",
33+
"test:watch": "vitest"
3034
}
3135
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { defineConfig } from 'vitest/config';
2+
import { resolve } from 'path';
3+
4+
export default defineConfig({
5+
test: {
6+
globals: true,
7+
environment: 'node',
8+
},
9+
resolve: {
10+
alias: {
11+
'@elizaos/core': resolve(__dirname, '../core/src'),
12+
},
13+
},
14+
});

0 commit comments

Comments
 (0)