Skip to content

Commit

Permalink
Merge pull request #1959 from ai16z-demirix/test/client-twitter
Browse files Browse the repository at this point in the history
test: adding tests for twitter-client
  • Loading branch information
shakkernerd authored Jan 7, 2025
2 parents 25fba34 + 846ae3f commit 49d76c3
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 2 deletions.
76 changes: 76 additions & 0 deletions packages/client-twitter/__tests__/base.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ClientBase } from '../src/base';
import { IAgentRuntime } from '@elizaos/core';
import { TwitterConfig } from '../src/environment';

describe('Twitter Client Base', () => {
let mockRuntime: IAgentRuntime;
let mockConfig: TwitterConfig;

beforeEach(() => {
mockRuntime = {
env: {
TWITTER_USERNAME: 'testuser',
TWITTER_DRY_RUN: 'true',
TWITTER_POST_INTERVAL_MIN: '5',
TWITTER_POST_INTERVAL_MAX: '10',
TWITTER_ACTION_INTERVAL: '5',
TWITTER_ENABLE_ACTION_PROCESSING: 'true',
TWITTER_POST_IMMEDIATELY: 'false',
TWITTER_SEARCH_ENABLE: 'false'
},
getEnv: function (key: string) {
return this.env[key] || null;
},
getSetting: function (key: string) {
return this.env[key] || null;
},
character: {
style: {
all: ['Test style 1', 'Test style 2'],
post: ['Post style 1', 'Post style 2']
}
}
} as unknown as IAgentRuntime;

mockConfig = {
TWITTER_USERNAME: 'testuser',
TWITTER_DRY_RUN: true,
TWITTER_SEARCH_ENABLE: false,
TWITTER_SPACES_ENABLE: false,
TWITTER_TARGET_USERS: [],
TWITTER_MAX_TWEETS_PER_DAY: 10,
TWITTER_MAX_TWEET_LENGTH: 280,
POST_INTERVAL_MIN: 5,
POST_INTERVAL_MAX: 10,
ACTION_INTERVAL: 5,
ENABLE_ACTION_PROCESSING: true,
POST_IMMEDIATELY: false
};
});

it('should create instance with correct configuration', () => {
const client = new ClientBase(mockRuntime, mockConfig);
expect(client).toBeDefined();
expect(client.twitterConfig).toBeDefined();
expect(client.twitterConfig.TWITTER_USERNAME).toBe('testuser');
expect(client.twitterConfig.TWITTER_DRY_RUN).toBe(true);
});

it('should initialize with correct tweet length limit', () => {
const client = new ClientBase(mockRuntime, mockConfig);
expect(client.twitterConfig.TWITTER_MAX_TWEET_LENGTH).toBe(280);
});

it('should initialize with correct post intervals', () => {
const client = new ClientBase(mockRuntime, mockConfig);
expect(client.twitterConfig.POST_INTERVAL_MIN).toBe(5);
expect(client.twitterConfig.POST_INTERVAL_MAX).toBe(10);
});

it('should initialize with correct action settings', () => {
const client = new ClientBase(mockRuntime, mockConfig);
expect(client.twitterConfig.ACTION_INTERVAL).toBe(5);
expect(client.twitterConfig.ENABLE_ACTION_PROCESSING).toBe(true);
});
});
134 changes: 134 additions & 0 deletions packages/client-twitter/__tests__/environment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { describe, it, expect, vi } from 'vitest';
import { validateTwitterConfig } from '../src/environment';
import { IAgentRuntime } from '@elizaos/core';

describe('Twitter Environment Configuration', () => {
const mockRuntime: IAgentRuntime = {
env: {
TWITTER_USERNAME: 'testuser123',
TWITTER_DRY_RUN: 'true',
TWITTER_SEARCH_ENABLE: 'false',
TWITTER_SPACES_ENABLE: 'false',
TWITTER_TARGET_USERS: 'user1,user2,user3',
TWITTER_MAX_TWEETS_PER_DAY: '10',
TWITTER_MAX_TWEET_LENGTH: '280',
TWITTER_POST_INTERVAL_MIN: '90',
TWITTER_POST_INTERVAL_MAX: '180',
TWITTER_ACTION_INTERVAL: '5',
TWITTER_ENABLE_ACTION_PROCESSING: 'false',
TWITTER_POST_IMMEDIATELY: 'false',
TWITTER_EMAIL: '[email protected]',
TWITTER_PASSWORD: 'hashedpassword',
TWITTER_2FA_SECRET: '',
TWITTER_POLL_INTERVAL: '120',
TWITTER_RETRY_LIMIT: '5',
ACTION_TIMELINE_TYPE: 'foryou',
MAX_ACTIONS_PROCESSING: '1',
MAX_TWEET_LENGTH: '280'
},
getEnv: function (key: string) {
return this.env[key] || null;
},
getSetting: function (key: string) {
return this.env[key] || null;
}
} as unknown as IAgentRuntime;

it('should validate correct configuration', async () => {
const config = await validateTwitterConfig(mockRuntime);
expect(config).toBeDefined();
expect(config.TWITTER_USERNAME).toBe('testuser123');
expect(config.TWITTER_DRY_RUN).toBe(true);
expect(config.TWITTER_SEARCH_ENABLE).toBe(false);
expect(config.TWITTER_SPACES_ENABLE).toBe(false);
expect(config.TWITTER_TARGET_USERS).toEqual(['user1', 'user2', 'user3']);
expect(config.MAX_TWEET_LENGTH).toBe(280);
expect(config.POST_INTERVAL_MIN).toBe(90);
expect(config.POST_INTERVAL_MAX).toBe(180);
expect(config.ACTION_INTERVAL).toBe(5);
expect(config.ENABLE_ACTION_PROCESSING).toBe(false);
expect(config.POST_IMMEDIATELY).toBe(false);
});

it('should validate wildcard username', async () => {
const wildcardRuntime = {
...mockRuntime,
env: {
...mockRuntime.env,
TWITTER_USERNAME: '*'
},
getEnv: function(key: string) {
return this.env[key] || null;
},
getSetting: function(key: string) {
return this.env[key] || null;
}
} as IAgentRuntime;

const config = await validateTwitterConfig(wildcardRuntime);
expect(config.TWITTER_USERNAME).toBe('*');
});

it('should validate username with numbers and underscores', async () => {
const validRuntime = {
...mockRuntime,
env: {
...mockRuntime.env,
TWITTER_USERNAME: 'test_user_123'
},
getEnv: function(key: string) {
return this.env[key] || null;
},
getSetting: function(key: string) {
return this.env[key] || null;
}
} as IAgentRuntime;

const config = await validateTwitterConfig(validRuntime);
expect(config.TWITTER_USERNAME).toBe('test_user_123');
});

it('should handle empty target users', async () => {
const runtimeWithoutTargets = {
...mockRuntime,
env: {
...mockRuntime.env,
TWITTER_TARGET_USERS: ''
},
getEnv: function(key: string) {
return this.env[key] || null;
},
getSetting: function(key: string) {
return this.env[key] || null;
}
} as IAgentRuntime;

const config = await validateTwitterConfig(runtimeWithoutTargets);
expect(config.TWITTER_TARGET_USERS).toHaveLength(0);
});

it('should use default values when optional configs are missing', async () => {
const minimalRuntime = {
env: {
TWITTER_USERNAME: 'testuser',
TWITTER_DRY_RUN: 'true',
TWITTER_EMAIL: '[email protected]',
TWITTER_PASSWORD: 'hashedpassword',
TWITTER_2FA_SECRET: '',
MAX_TWEET_LENGTH: '280'
},
getEnv: function (key: string) {
return this.env[key] || null;
},
getSetting: function (key: string) {
return this.env[key] || null;
}
} as unknown as IAgentRuntime;

const config = await validateTwitterConfig(minimalRuntime);
expect(config).toBeDefined();
expect(config.MAX_TWEET_LENGTH).toBe(280);
expect(config.POST_INTERVAL_MIN).toBe(90);
expect(config.POST_INTERVAL_MAX).toBe(180);
});
});
97 changes: 97 additions & 0 deletions packages/client-twitter/__tests__/post.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { describe, it, expect, vi } from 'vitest';
import { TwitterPostClient } from '../src/post';
import { ClientBase } from '../src/base';
import { IAgentRuntime } from '@elizaos/core';
import { TwitterConfig } from '../src/environment';

describe('Twitter Post Client', () => {
let mockRuntime: IAgentRuntime;
let mockConfig: TwitterConfig;
let baseClient: ClientBase;

beforeEach(() => {
mockRuntime = {
env: {
TWITTER_USERNAME: 'testuser',
TWITTER_DRY_RUN: 'true',
TWITTER_POST_INTERVAL_MIN: '5',
TWITTER_POST_INTERVAL_MAX: '10',
TWITTER_ACTION_INTERVAL: '5',
TWITTER_ENABLE_ACTION_PROCESSING: 'true',
TWITTER_POST_IMMEDIATELY: 'false',
TWITTER_SEARCH_ENABLE: 'false',
TWITTER_EMAIL: '[email protected]',
TWITTER_PASSWORD: 'hashedpassword',
TWITTER_2FA_SECRET: '',
TWITTER_POLL_INTERVAL: '120',
TWITTER_RETRY_LIMIT: '5',
ACTION_TIMELINE_TYPE: 'foryou',
MAX_ACTIONS_PROCESSING: '1',
MAX_TWEET_LENGTH: '280'
},
getEnv: function (key: string) {
return this.env[key] || null;
},
getSetting: function (key: string) {
return this.env[key] || null;
},
character: {
style: {
all: ['Test style 1', 'Test style 2'],
post: ['Post style 1', 'Post style 2']
}
}
} as unknown as IAgentRuntime;

mockConfig = {
TWITTER_USERNAME: 'testuser',
TWITTER_DRY_RUN: true,
TWITTER_SEARCH_ENABLE: false,
TWITTER_SPACES_ENABLE: false,
TWITTER_TARGET_USERS: [],
TWITTER_MAX_TWEETS_PER_DAY: 10,
TWITTER_MAX_TWEET_LENGTH: 280,
POST_INTERVAL_MIN: 5,
POST_INTERVAL_MAX: 10,
ACTION_INTERVAL: 5,
ENABLE_ACTION_PROCESSING: true,
POST_IMMEDIATELY: false,
MAX_TWEET_LENGTH: 280
};

baseClient = new ClientBase(mockRuntime, mockConfig);
});

it('should create post client instance', () => {
const postClient = new TwitterPostClient(baseClient, mockRuntime);
expect(postClient).toBeDefined();
expect(postClient.twitterUsername).toBe('testuser');
expect(postClient['isDryRun']).toBe(true);
});

it('should keep tweets under max length when already valid', () => {
const postClient = new TwitterPostClient(baseClient, mockRuntime);
const validTweet = 'This is a valid tweet';
const result = postClient['trimTweetLength'](validTweet);
expect(result).toBe(validTweet);
expect(result.length).toBeLessThanOrEqual(280);
});

it('should cut at last sentence when possible', () => {
const postClient = new TwitterPostClient(baseClient, mockRuntime);
const longTweet = 'First sentence. Second sentence that is quite long. Third sentence that would make it too long.';
const result = postClient['trimTweetLength'](longTweet);
const lastPeriod = result.lastIndexOf('.');
expect(lastPeriod).toBeGreaterThan(0);
expect(result.length).toBeLessThanOrEqual(280);
});

it('should add ellipsis when cutting within a sentence', () => {
const postClient = new TwitterPostClient(baseClient, mockRuntime);
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';
const result = postClient['trimTweetLength'](longSentence);
const lastSpace = result.lastIndexOf(' ');
expect(lastSpace).toBeGreaterThan(0);
expect(result.length).toBeLessThanOrEqual(280);
});
});
8 changes: 6 additions & 2 deletions packages/client-twitter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@
"zod": "3.23.8"
},
"devDependencies": {
"tsup": "8.3.5"
"tsup": "8.3.5",
"vitest": "1.1.3",
"@vitest/coverage-v8": "1.1.3"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache ."
"lint": "eslint --fix --cache .",
"test": "vitest run",
"test:coverage": "vitest run --coverage"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
Expand Down
12 changes: 12 additions & 0 deletions packages/client-twitter/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['__tests__/**/*.test.ts'],
coverage: {
reporter: ['text', 'json', 'html'],
},
},
});

0 comments on commit 49d76c3

Please sign in to comment.