Skip to content

Commit 3481049

Browse files
authored
Merge branch 'develop' into giphy-plugin
2 parents bfae6be + 49d76c3 commit 3481049

File tree

6 files changed

+384
-8
lines changed

6 files changed

+384
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { ClientBase } from '../src/base';
3+
import { IAgentRuntime } from '@elizaos/core';
4+
import { TwitterConfig } from '../src/environment';
5+
6+
describe('Twitter Client Base', () => {
7+
let mockRuntime: IAgentRuntime;
8+
let mockConfig: TwitterConfig;
9+
10+
beforeEach(() => {
11+
mockRuntime = {
12+
env: {
13+
TWITTER_USERNAME: 'testuser',
14+
TWITTER_DRY_RUN: 'true',
15+
TWITTER_POST_INTERVAL_MIN: '5',
16+
TWITTER_POST_INTERVAL_MAX: '10',
17+
TWITTER_ACTION_INTERVAL: '5',
18+
TWITTER_ENABLE_ACTION_PROCESSING: 'true',
19+
TWITTER_POST_IMMEDIATELY: 'false',
20+
TWITTER_SEARCH_ENABLE: 'false'
21+
},
22+
getEnv: function (key: string) {
23+
return this.env[key] || null;
24+
},
25+
getSetting: function (key: string) {
26+
return this.env[key] || null;
27+
},
28+
character: {
29+
style: {
30+
all: ['Test style 1', 'Test style 2'],
31+
post: ['Post style 1', 'Post style 2']
32+
}
33+
}
34+
} as unknown as IAgentRuntime;
35+
36+
mockConfig = {
37+
TWITTER_USERNAME: 'testuser',
38+
TWITTER_DRY_RUN: true,
39+
TWITTER_SEARCH_ENABLE: false,
40+
TWITTER_SPACES_ENABLE: false,
41+
TWITTER_TARGET_USERS: [],
42+
TWITTER_MAX_TWEETS_PER_DAY: 10,
43+
TWITTER_MAX_TWEET_LENGTH: 280,
44+
POST_INTERVAL_MIN: 5,
45+
POST_INTERVAL_MAX: 10,
46+
ACTION_INTERVAL: 5,
47+
ENABLE_ACTION_PROCESSING: true,
48+
POST_IMMEDIATELY: false
49+
};
50+
});
51+
52+
it('should create instance with correct configuration', () => {
53+
const client = new ClientBase(mockRuntime, mockConfig);
54+
expect(client).toBeDefined();
55+
expect(client.twitterConfig).toBeDefined();
56+
expect(client.twitterConfig.TWITTER_USERNAME).toBe('testuser');
57+
expect(client.twitterConfig.TWITTER_DRY_RUN).toBe(true);
58+
});
59+
60+
it('should initialize with correct tweet length limit', () => {
61+
const client = new ClientBase(mockRuntime, mockConfig);
62+
expect(client.twitterConfig.TWITTER_MAX_TWEET_LENGTH).toBe(280);
63+
});
64+
65+
it('should initialize with correct post intervals', () => {
66+
const client = new ClientBase(mockRuntime, mockConfig);
67+
expect(client.twitterConfig.POST_INTERVAL_MIN).toBe(5);
68+
expect(client.twitterConfig.POST_INTERVAL_MAX).toBe(10);
69+
});
70+
71+
it('should initialize with correct action settings', () => {
72+
const client = new ClientBase(mockRuntime, mockConfig);
73+
expect(client.twitterConfig.ACTION_INTERVAL).toBe(5);
74+
expect(client.twitterConfig.ENABLE_ACTION_PROCESSING).toBe(true);
75+
});
76+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { validateTwitterConfig } from '../src/environment';
3+
import { IAgentRuntime } from '@elizaos/core';
4+
5+
describe('Twitter Environment Configuration', () => {
6+
const mockRuntime: IAgentRuntime = {
7+
env: {
8+
TWITTER_USERNAME: 'testuser123',
9+
TWITTER_DRY_RUN: 'true',
10+
TWITTER_SEARCH_ENABLE: 'false',
11+
TWITTER_SPACES_ENABLE: 'false',
12+
TWITTER_TARGET_USERS: 'user1,user2,user3',
13+
TWITTER_MAX_TWEETS_PER_DAY: '10',
14+
TWITTER_MAX_TWEET_LENGTH: '280',
15+
TWITTER_POST_INTERVAL_MIN: '90',
16+
TWITTER_POST_INTERVAL_MAX: '180',
17+
TWITTER_ACTION_INTERVAL: '5',
18+
TWITTER_ENABLE_ACTION_PROCESSING: 'false',
19+
TWITTER_POST_IMMEDIATELY: 'false',
20+
TWITTER_EMAIL: 'test@example.com',
21+
TWITTER_PASSWORD: 'hashedpassword',
22+
TWITTER_2FA_SECRET: '',
23+
TWITTER_POLL_INTERVAL: '120',
24+
TWITTER_RETRY_LIMIT: '5',
25+
ACTION_TIMELINE_TYPE: 'foryou',
26+
MAX_ACTIONS_PROCESSING: '1',
27+
MAX_TWEET_LENGTH: '280'
28+
},
29+
getEnv: function (key: string) {
30+
return this.env[key] || null;
31+
},
32+
getSetting: function (key: string) {
33+
return this.env[key] || null;
34+
}
35+
} as unknown as IAgentRuntime;
36+
37+
it('should validate correct configuration', async () => {
38+
const config = await validateTwitterConfig(mockRuntime);
39+
expect(config).toBeDefined();
40+
expect(config.TWITTER_USERNAME).toBe('testuser123');
41+
expect(config.TWITTER_DRY_RUN).toBe(true);
42+
expect(config.TWITTER_SEARCH_ENABLE).toBe(false);
43+
expect(config.TWITTER_SPACES_ENABLE).toBe(false);
44+
expect(config.TWITTER_TARGET_USERS).toEqual(['user1', 'user2', 'user3']);
45+
expect(config.MAX_TWEET_LENGTH).toBe(280);
46+
expect(config.POST_INTERVAL_MIN).toBe(90);
47+
expect(config.POST_INTERVAL_MAX).toBe(180);
48+
expect(config.ACTION_INTERVAL).toBe(5);
49+
expect(config.ENABLE_ACTION_PROCESSING).toBe(false);
50+
expect(config.POST_IMMEDIATELY).toBe(false);
51+
});
52+
53+
it('should validate wildcard username', async () => {
54+
const wildcardRuntime = {
55+
...mockRuntime,
56+
env: {
57+
...mockRuntime.env,
58+
TWITTER_USERNAME: '*'
59+
},
60+
getEnv: function(key: string) {
61+
return this.env[key] || null;
62+
},
63+
getSetting: function(key: string) {
64+
return this.env[key] || null;
65+
}
66+
} as IAgentRuntime;
67+
68+
const config = await validateTwitterConfig(wildcardRuntime);
69+
expect(config.TWITTER_USERNAME).toBe('*');
70+
});
71+
72+
it('should validate username with numbers and underscores', async () => {
73+
const validRuntime = {
74+
...mockRuntime,
75+
env: {
76+
...mockRuntime.env,
77+
TWITTER_USERNAME: 'test_user_123'
78+
},
79+
getEnv: function(key: string) {
80+
return this.env[key] || null;
81+
},
82+
getSetting: function(key: string) {
83+
return this.env[key] || null;
84+
}
85+
} as IAgentRuntime;
86+
87+
const config = await validateTwitterConfig(validRuntime);
88+
expect(config.TWITTER_USERNAME).toBe('test_user_123');
89+
});
90+
91+
it('should handle empty target users', async () => {
92+
const runtimeWithoutTargets = {
93+
...mockRuntime,
94+
env: {
95+
...mockRuntime.env,
96+
TWITTER_TARGET_USERS: ''
97+
},
98+
getEnv: function(key: string) {
99+
return this.env[key] || null;
100+
},
101+
getSetting: function(key: string) {
102+
return this.env[key] || null;
103+
}
104+
} as IAgentRuntime;
105+
106+
const config = await validateTwitterConfig(runtimeWithoutTargets);
107+
expect(config.TWITTER_TARGET_USERS).toHaveLength(0);
108+
});
109+
110+
it('should use default values when optional configs are missing', async () => {
111+
const minimalRuntime = {
112+
env: {
113+
TWITTER_USERNAME: 'testuser',
114+
TWITTER_DRY_RUN: 'true',
115+
TWITTER_EMAIL: 'test@example.com',
116+
TWITTER_PASSWORD: 'hashedpassword',
117+
TWITTER_2FA_SECRET: '',
118+
MAX_TWEET_LENGTH: '280'
119+
},
120+
getEnv: function (key: string) {
121+
return this.env[key] || null;
122+
},
123+
getSetting: function (key: string) {
124+
return this.env[key] || null;
125+
}
126+
} as unknown as IAgentRuntime;
127+
128+
const config = await validateTwitterConfig(minimalRuntime);
129+
expect(config).toBeDefined();
130+
expect(config.MAX_TWEET_LENGTH).toBe(280);
131+
expect(config.POST_INTERVAL_MIN).toBe(90);
132+
expect(config.POST_INTERVAL_MAX).toBe(180);
133+
});
134+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { TwitterPostClient } from '../src/post';
3+
import { ClientBase } from '../src/base';
4+
import { IAgentRuntime } from '@elizaos/core';
5+
import { TwitterConfig } from '../src/environment';
6+
7+
describe('Twitter Post Client', () => {
8+
let mockRuntime: IAgentRuntime;
9+
let mockConfig: TwitterConfig;
10+
let baseClient: ClientBase;
11+
12+
beforeEach(() => {
13+
mockRuntime = {
14+
env: {
15+
TWITTER_USERNAME: 'testuser',
16+
TWITTER_DRY_RUN: 'true',
17+
TWITTER_POST_INTERVAL_MIN: '5',
18+
TWITTER_POST_INTERVAL_MAX: '10',
19+
TWITTER_ACTION_INTERVAL: '5',
20+
TWITTER_ENABLE_ACTION_PROCESSING: 'true',
21+
TWITTER_POST_IMMEDIATELY: 'false',
22+
TWITTER_SEARCH_ENABLE: 'false',
23+
TWITTER_EMAIL: 'test@example.com',
24+
TWITTER_PASSWORD: 'hashedpassword',
25+
TWITTER_2FA_SECRET: '',
26+
TWITTER_POLL_INTERVAL: '120',
27+
TWITTER_RETRY_LIMIT: '5',
28+
ACTION_TIMELINE_TYPE: 'foryou',
29+
MAX_ACTIONS_PROCESSING: '1',
30+
MAX_TWEET_LENGTH: '280'
31+
},
32+
getEnv: function (key: string) {
33+
return this.env[key] || null;
34+
},
35+
getSetting: function (key: string) {
36+
return this.env[key] || null;
37+
},
38+
character: {
39+
style: {
40+
all: ['Test style 1', 'Test style 2'],
41+
post: ['Post style 1', 'Post style 2']
42+
}
43+
}
44+
} as unknown as IAgentRuntime;
45+
46+
mockConfig = {
47+
TWITTER_USERNAME: 'testuser',
48+
TWITTER_DRY_RUN: true,
49+
TWITTER_SEARCH_ENABLE: false,
50+
TWITTER_SPACES_ENABLE: false,
51+
TWITTER_TARGET_USERS: [],
52+
TWITTER_MAX_TWEETS_PER_DAY: 10,
53+
TWITTER_MAX_TWEET_LENGTH: 280,
54+
POST_INTERVAL_MIN: 5,
55+
POST_INTERVAL_MAX: 10,
56+
ACTION_INTERVAL: 5,
57+
ENABLE_ACTION_PROCESSING: true,
58+
POST_IMMEDIATELY: false,
59+
MAX_TWEET_LENGTH: 280
60+
};
61+
62+
baseClient = new ClientBase(mockRuntime, mockConfig);
63+
});
64+
65+
it('should create post client instance', () => {
66+
const postClient = new TwitterPostClient(baseClient, mockRuntime);
67+
expect(postClient).toBeDefined();
68+
expect(postClient.twitterUsername).toBe('testuser');
69+
expect(postClient['isDryRun']).toBe(true);
70+
});
71+
72+
it('should keep tweets under max length when already valid', () => {
73+
const postClient = new TwitterPostClient(baseClient, mockRuntime);
74+
const validTweet = 'This is a valid tweet';
75+
const result = postClient['trimTweetLength'](validTweet);
76+
expect(result).toBe(validTweet);
77+
expect(result.length).toBeLessThanOrEqual(280);
78+
});
79+
80+
it('should cut at last sentence when possible', () => {
81+
const postClient = new TwitterPostClient(baseClient, mockRuntime);
82+
const longTweet = 'First sentence. Second sentence that is quite long. Third sentence that would make it too long.';
83+
const result = postClient['trimTweetLength'](longTweet);
84+
const lastPeriod = result.lastIndexOf('.');
85+
expect(lastPeriod).toBeGreaterThan(0);
86+
expect(result.length).toBeLessThanOrEqual(280);
87+
});
88+
89+
it('should add ellipsis when cutting within a sentence', () => {
90+
const postClient = new TwitterPostClient(baseClient, mockRuntime);
91+
const longSentence = 'This is an extremely long sentence without any periods that needs to be truncated because it exceeds the maximum allowed length for a tweet on the Twitter platform and therefore must be shortened';
92+
const result = postClient['trimTweetLength'](longSentence);
93+
const lastSpace = result.lastIndexOf(' ');
94+
expect(lastSpace).toBeGreaterThan(0);
95+
expect(result.length).toBeLessThanOrEqual(280);
96+
});
97+
});

packages/client-twitter/package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@
2525
"zod": "3.23.8"
2626
},
2727
"devDependencies": {
28-
"tsup": "8.3.5"
28+
"tsup": "8.3.5",
29+
"vitest": "1.1.3",
30+
"@vitest/coverage-v8": "1.1.3"
2931
},
3032
"scripts": {
3133
"build": "tsup --format esm --dts",
3234
"dev": "tsup --format esm --dts --watch",
33-
"lint": "eslint --fix --cache ."
35+
"lint": "eslint --fix --cache .",
36+
"test": "vitest run",
37+
"test:coverage": "vitest run --coverage"
3438
},
3539
"peerDependencies": {
3640
"whatwg-url": "7.1.0"

0 commit comments

Comments
 (0)