Skip to content

Commit 3a69164

Browse files
test configuration and tests for client-lens (elizaOS#2534)
* client-lens: test configuration * client-lens: test utils * client-lens: client tests * client-lens: interaction tests * client-lens: post tests
1 parent 227d9de commit 3a69164

File tree

7 files changed

+404
-7
lines changed

7 files changed

+404
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { LensClient } from '../src/client';
3+
import { LensClient as LensClientCore, LimitType, PublicationType } from '@lens-protocol/client';
4+
5+
// Mock dependencies
6+
vi.mock('@lens-protocol/client', async () => {
7+
const actual = await vi.importActual('@lens-protocol/client');
8+
return {
9+
...actual,
10+
LensClient: vi.fn().mockImplementation(() => ({
11+
authentication: {
12+
generateChallenge: vi.fn().mockResolvedValue({ id: 'challenge-id', text: 'challenge-text' }),
13+
authenticate: vi.fn().mockResolvedValue({ accessToken: 'mock-token', refreshToken: 'mock-refresh' })
14+
},
15+
profile: {
16+
fetch: vi.fn().mockResolvedValue({
17+
id: '0x01',
18+
handle: { localName: 'test.lens' },
19+
metadata: {
20+
displayName: 'Test User',
21+
bio: 'Test bio',
22+
picture: {
23+
uri: 'https://example.com/pic-raw.jpg'
24+
}
25+
}
26+
})
27+
},
28+
publication: {
29+
fetchAll: vi.fn().mockResolvedValue({
30+
items: [
31+
{
32+
id: 'pub-1',
33+
metadata: { content: 'Test post' },
34+
stats: { reactions: 10 }
35+
}
36+
]
37+
})
38+
}
39+
}))
40+
};
41+
});
42+
43+
describe('LensClient', () => {
44+
let client: LensClient;
45+
const mockRuntime = {
46+
name: 'test-runtime',
47+
memory: new Map(),
48+
getMemory: vi.fn(),
49+
setMemory: vi.fn(),
50+
clearMemory: vi.fn()
51+
};
52+
const mockAccount = {
53+
address: '0x123' as `0x${string}`,
54+
privateKey: '0xabc' as `0x${string}`,
55+
signMessage: vi.fn().mockResolvedValue('signed-message'),
56+
signTypedData: vi.fn()
57+
};
58+
59+
beforeEach(() => {
60+
vi.clearAllMocks();
61+
client = new LensClient({
62+
runtime: mockRuntime,
63+
cache: new Map(),
64+
account: mockAccount,
65+
profileId: '0x01' as `0x${string}`
66+
});
67+
});
68+
69+
describe('authenticate', () => {
70+
it('should authenticate successfully', async () => {
71+
await client.authenticate();
72+
expect(client['authenticated']).toBe(true);
73+
expect(client['core'].authentication.generateChallenge).toHaveBeenCalledWith({
74+
signedBy: mockAccount.address,
75+
for: '0x01'
76+
});
77+
expect(mockAccount.signMessage).toHaveBeenCalledWith({ message: 'challenge-text' });
78+
});
79+
80+
it('should handle authentication errors', async () => {
81+
const mockError = new Error('Auth failed');
82+
vi.mocked(client['core'].authentication.generateChallenge).mockRejectedValueOnce(mockError);
83+
84+
await expect(client.authenticate()).rejects.toThrow('Auth failed');
85+
expect(client['authenticated']).toBe(false);
86+
});
87+
});
88+
89+
describe('getPublicationsFor', () => {
90+
it('should fetch publications successfully', async () => {
91+
const publications = await client.getPublicationsFor('0x123');
92+
expect(publications).toHaveLength(1);
93+
expect(publications[0].id).toBe('pub-1');
94+
expect(client['core'].publication.fetchAll).toHaveBeenCalledWith({
95+
limit: LimitType.Fifty,
96+
where: {
97+
from: ['0x123'],
98+
publicationTypes: [PublicationType.Post]
99+
}
100+
});
101+
});
102+
103+
it('should handle fetch errors', async () => {
104+
vi.mocked(client['core'].publication.fetchAll).mockRejectedValueOnce(new Error('Fetch failed'));
105+
await expect(client.getPublicationsFor('0x123')).rejects.toThrow('Fetch failed');
106+
});
107+
});
108+
109+
describe('getProfile', () => {
110+
it('should fetch profile successfully', async () => {
111+
const profile = await client.getProfile('0x123');
112+
expect(profile).toBeDefined();
113+
expect(profile.id).toBe('0x01');
114+
expect(profile.handle).toBe('test.lens');
115+
expect(profile.pfp).toBe('https://example.com/pic-raw.jpg');
116+
expect(client['core'].profile.fetch).toHaveBeenCalledWith({ forProfileId: '0x123' });
117+
});
118+
119+
it('should handle profile fetch errors', async () => {
120+
vi.mocked(client['core'].profile.fetch).mockRejectedValueOnce(new Error('Profile fetch failed'));
121+
await expect(client.getProfile('0x123')).rejects.toThrow('Profile fetch failed');
122+
});
123+
});
124+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { createTestInteraction, handleTestInteraction } from './test-utils';
3+
import { LensClient } from '../src/client';
4+
import type { AnyPublicationFragment, ProfileFragment } from '@lens-protocol/client';
5+
6+
// Mock LensClient
7+
vi.mock('../src/client', () => ({
8+
LensClient: vi.fn().mockImplementation(() => ({
9+
authenticate: vi.fn().mockResolvedValue(undefined),
10+
mirror: vi.fn().mockResolvedValue({ id: 'mirror-1' }),
11+
comment: vi.fn().mockResolvedValue({ id: 'comment-1' }),
12+
like: vi.fn().mockResolvedValue({ id: 'like-1' }),
13+
follow: vi.fn().mockResolvedValue({ id: 'follow-1' })
14+
}))
15+
}));
16+
17+
describe('Interactions', () => {
18+
const mockPublication = {
19+
id: 'pub-1',
20+
metadata: {
21+
content: 'Test publication'
22+
},
23+
stats: {
24+
totalAmountOfMirrors: 5,
25+
totalAmountOfComments: 3,
26+
totalUpvotes: 10
27+
}
28+
} as unknown as AnyPublicationFragment;
29+
30+
const mockProfile = {
31+
id: '0x01',
32+
handle: 'test.lens',
33+
stats: {
34+
totalFollowers: 100,
35+
totalFollowing: 50
36+
}
37+
} as unknown as ProfileFragment;
38+
39+
describe('createTestInteraction', () => {
40+
it('should create mirror interaction when conditions are met', () => {
41+
const interaction = createTestInteraction(mockPublication, mockProfile);
42+
expect(interaction).toBeDefined();
43+
if (interaction) {
44+
expect(['MIRROR', 'COMMENT', 'LIKE', 'FOLLOW']).toContain(interaction.type);
45+
}
46+
});
47+
48+
it('should return null when no interaction is needed', () => {
49+
const lowStatsPublication = {
50+
...mockPublication,
51+
stats: {
52+
totalAmountOfMirrors: 0,
53+
totalAmountOfComments: 0,
54+
totalUpvotes: 0
55+
}
56+
} as unknown as AnyPublicationFragment;
57+
const interaction = createTestInteraction(lowStatsPublication, mockProfile);
58+
expect(interaction).toBeNull();
59+
});
60+
});
61+
62+
describe('handleTestInteraction', () => {
63+
let client: LensClient;
64+
65+
beforeEach(() => {
66+
vi.clearAllMocks();
67+
client = new LensClient({
68+
runtime: {
69+
name: 'test-runtime',
70+
memory: new Map(),
71+
getMemory: vi.fn(),
72+
setMemory: vi.fn(),
73+
clearMemory: vi.fn()
74+
},
75+
cache: new Map(),
76+
account: {
77+
address: '0x123' as `0x${string}`,
78+
privateKey: '0xabc' as `0x${string}`,
79+
signMessage: vi.fn(),
80+
signTypedData: vi.fn()
81+
},
82+
profileId: '0x01' as `0x${string}`
83+
});
84+
});
85+
86+
it('should handle mirror interaction successfully', async () => {
87+
const interaction = {
88+
type: 'MIRROR' as const,
89+
publicationId: 'pub-1'
90+
};
91+
92+
const result = await handleTestInteraction(client, interaction);
93+
expect(result).toBeDefined();
94+
expect(result.id).toBe('mirror-1');
95+
expect(client.mirror).toHaveBeenCalledWith('pub-1');
96+
});
97+
98+
it('should handle comment interaction successfully', async () => {
99+
const interaction = {
100+
type: 'COMMENT' as const,
101+
publicationId: 'pub-1',
102+
content: 'Test comment'
103+
};
104+
105+
const result = await handleTestInteraction(client, interaction);
106+
expect(result).toBeDefined();
107+
expect(result.id).toBe('comment-1');
108+
expect(client.comment).toHaveBeenCalledWith('pub-1', 'Test comment');
109+
});
110+
111+
it('should handle interaction errors', async () => {
112+
const interaction = {
113+
type: 'MIRROR' as const,
114+
publicationId: 'pub-1'
115+
};
116+
117+
vi.mocked(client.mirror).mockRejectedValueOnce(new Error('Mirror failed'));
118+
await expect(handleTestInteraction(client, interaction)).rejects.toThrow('Mirror failed');
119+
});
120+
});
121+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { createTestPost } from './test-utils';
3+
import { LensClient } from '../src/client';
4+
5+
// Mock dependencies
6+
vi.mock('../src/client', () => ({
7+
LensClient: vi.fn().mockImplementation(() => ({
8+
authenticate: vi.fn().mockResolvedValue(undefined),
9+
post: vi.fn().mockResolvedValue({ id: 'post-1' })
10+
}))
11+
}));
12+
13+
describe('Post Functions', () => {
14+
let client: LensClient;
15+
16+
beforeEach(() => {
17+
vi.clearAllMocks();
18+
client = new LensClient({
19+
runtime: {
20+
name: 'test-runtime',
21+
memory: new Map(),
22+
getMemory: vi.fn(),
23+
setMemory: vi.fn(),
24+
clearMemory: vi.fn()
25+
},
26+
cache: new Map(),
27+
account: {
28+
address: '0x123' as `0x${string}`,
29+
privateKey: '0xabc' as `0x${string}`,
30+
signMessage: vi.fn(),
31+
signTypedData: vi.fn()
32+
},
33+
profileId: '0x01' as `0x${string}`
34+
});
35+
});
36+
37+
describe('createTestPost', () => {
38+
it('should create a post successfully', async () => {
39+
const content = 'Test post content';
40+
const result = await createTestPost(client, content);
41+
42+
expect(result).toBeDefined();
43+
expect(result.id).toBe('post-1');
44+
expect(client.post).toHaveBeenCalledWith(content);
45+
});
46+
47+
it('should handle post creation errors', async () => {
48+
const content = 'Test post content';
49+
vi.mocked(client.post).mockRejectedValueOnce(new Error('Post creation failed'));
50+
51+
await expect(createTestPost(client, content)).rejects.toThrow('Post creation failed');
52+
});
53+
54+
it('should handle empty content', async () => {
55+
const content = '';
56+
await expect(createTestPost(client, content)).rejects.toThrow('Post content cannot be empty');
57+
});
58+
59+
it('should handle very long content', async () => {
60+
const content = 'a'.repeat(5001); // Assuming max length is 5000
61+
await expect(createTestPost(client, content)).rejects.toThrow('Post content too long');
62+
});
63+
});
64+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { AnyPublicationFragment } from "@lens-protocol/client";
2+
import type { LensClient } from "../src/client";
3+
import type { Profile } from "../src/types";
4+
5+
export interface TestInteraction {
6+
type: 'MIRROR' | 'COMMENT' | 'LIKE' | 'FOLLOW';
7+
publicationId?: string;
8+
content?: string;
9+
}
10+
11+
export function createTestInteraction(publication: AnyPublicationFragment, profile: Profile): TestInteraction | null {
12+
const stats = publication.stats;
13+
14+
// Simple heuristic: if the publication has good engagement, mirror it
15+
if (stats.totalAmountOfMirrors > 3 || stats.totalAmountOfComments > 2 || stats.totalUpvotes > 5) {
16+
return {
17+
type: 'MIRROR',
18+
publicationId: publication.id
19+
};
20+
}
21+
22+
// If the publication is engaging but not viral, comment on it
23+
if (stats.totalAmountOfComments > 0 || stats.totalUpvotes > 2) {
24+
return {
25+
type: 'COMMENT',
26+
publicationId: publication.id,
27+
content: 'Interesting perspective!'
28+
};
29+
}
30+
31+
return null;
32+
}
33+
34+
export async function handleTestInteraction(client: LensClient, interaction: TestInteraction) {
35+
switch (interaction.type) {
36+
case 'MIRROR':
37+
if (!interaction.publicationId) throw new Error('Publication ID required for mirror');
38+
return await client.mirror(interaction.publicationId);
39+
case 'COMMENT':
40+
if (!interaction.publicationId || !interaction.content) {
41+
throw new Error('Publication ID and content required for comment');
42+
}
43+
return await client.comment(interaction.publicationId, interaction.content);
44+
case 'LIKE':
45+
if (!interaction.publicationId) throw new Error('Publication ID required for like');
46+
return await client.like(interaction.publicationId);
47+
case 'FOLLOW':
48+
if (!interaction.publicationId) throw new Error('Profile ID required for follow');
49+
return await client.follow(interaction.publicationId);
50+
default:
51+
throw new Error('Unknown interaction type');
52+
}
53+
}
54+
55+
export async function createTestPost(client: LensClient, content: string) {
56+
if (!content) {
57+
throw new Error('Post content cannot be empty');
58+
}
59+
if (content.length > 5000) {
60+
throw new Error('Post content too long');
61+
}
62+
return await client.post(content);
63+
}

0 commit comments

Comments
 (0)