Skip to content

Commit

Permalink
test configuration and tests for client-lens (elizaOS#2534)
Browse files Browse the repository at this point in the history
* client-lens: test configuration

* client-lens: test utils

* client-lens: client tests

* client-lens: interaction tests

* client-lens: post tests
  • Loading branch information
ai16z-demirix authored Jan 20, 2025
1 parent 227d9de commit 3a69164
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 7 deletions.
124 changes: 124 additions & 0 deletions packages/client-lens/__tests__/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { LensClient } from '../src/client';
import { LensClient as LensClientCore, LimitType, PublicationType } from '@lens-protocol/client';

// Mock dependencies
vi.mock('@lens-protocol/client', async () => {
const actual = await vi.importActual('@lens-protocol/client');
return {
...actual,
LensClient: vi.fn().mockImplementation(() => ({
authentication: {
generateChallenge: vi.fn().mockResolvedValue({ id: 'challenge-id', text: 'challenge-text' }),
authenticate: vi.fn().mockResolvedValue({ accessToken: 'mock-token', refreshToken: 'mock-refresh' })
},
profile: {
fetch: vi.fn().mockResolvedValue({
id: '0x01',
handle: { localName: 'test.lens' },
metadata: {
displayName: 'Test User',
bio: 'Test bio',
picture: {
uri: 'https://example.com/pic-raw.jpg'
}
}
})
},
publication: {
fetchAll: vi.fn().mockResolvedValue({
items: [
{
id: 'pub-1',
metadata: { content: 'Test post' },
stats: { reactions: 10 }
}
]
})
}
}))
};
});

describe('LensClient', () => {
let client: LensClient;
const mockRuntime = {
name: 'test-runtime',
memory: new Map(),
getMemory: vi.fn(),
setMemory: vi.fn(),
clearMemory: vi.fn()
};
const mockAccount = {
address: '0x123' as `0x${string}`,
privateKey: '0xabc' as `0x${string}`,
signMessage: vi.fn().mockResolvedValue('signed-message'),
signTypedData: vi.fn()
};

beforeEach(() => {
vi.clearAllMocks();
client = new LensClient({
runtime: mockRuntime,
cache: new Map(),
account: mockAccount,
profileId: '0x01' as `0x${string}`
});
});

describe('authenticate', () => {
it('should authenticate successfully', async () => {
await client.authenticate();
expect(client['authenticated']).toBe(true);
expect(client['core'].authentication.generateChallenge).toHaveBeenCalledWith({
signedBy: mockAccount.address,
for: '0x01'
});
expect(mockAccount.signMessage).toHaveBeenCalledWith({ message: 'challenge-text' });
});

it('should handle authentication errors', async () => {
const mockError = new Error('Auth failed');
vi.mocked(client['core'].authentication.generateChallenge).mockRejectedValueOnce(mockError);

await expect(client.authenticate()).rejects.toThrow('Auth failed');
expect(client['authenticated']).toBe(false);
});
});

describe('getPublicationsFor', () => {
it('should fetch publications successfully', async () => {
const publications = await client.getPublicationsFor('0x123');
expect(publications).toHaveLength(1);
expect(publications[0].id).toBe('pub-1');
expect(client['core'].publication.fetchAll).toHaveBeenCalledWith({
limit: LimitType.Fifty,
where: {
from: ['0x123'],
publicationTypes: [PublicationType.Post]
}
});
});

it('should handle fetch errors', async () => {
vi.mocked(client['core'].publication.fetchAll).mockRejectedValueOnce(new Error('Fetch failed'));
await expect(client.getPublicationsFor('0x123')).rejects.toThrow('Fetch failed');
});
});

describe('getProfile', () => {
it('should fetch profile successfully', async () => {
const profile = await client.getProfile('0x123');
expect(profile).toBeDefined();
expect(profile.id).toBe('0x01');
expect(profile.handle).toBe('test.lens');
expect(profile.pfp).toBe('https://example.com/pic-raw.jpg');
expect(client['core'].profile.fetch).toHaveBeenCalledWith({ forProfileId: '0x123' });
});

it('should handle profile fetch errors', async () => {
vi.mocked(client['core'].profile.fetch).mockRejectedValueOnce(new Error('Profile fetch failed'));
await expect(client.getProfile('0x123')).rejects.toThrow('Profile fetch failed');
});
});
});
121 changes: 121 additions & 0 deletions packages/client-lens/__tests__/interactions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createTestInteraction, handleTestInteraction } from './test-utils';
import { LensClient } from '../src/client';
import type { AnyPublicationFragment, ProfileFragment } from '@lens-protocol/client';

// Mock LensClient
vi.mock('../src/client', () => ({
LensClient: vi.fn().mockImplementation(() => ({
authenticate: vi.fn().mockResolvedValue(undefined),
mirror: vi.fn().mockResolvedValue({ id: 'mirror-1' }),
comment: vi.fn().mockResolvedValue({ id: 'comment-1' }),
like: vi.fn().mockResolvedValue({ id: 'like-1' }),
follow: vi.fn().mockResolvedValue({ id: 'follow-1' })
}))
}));

describe('Interactions', () => {
const mockPublication = {
id: 'pub-1',
metadata: {
content: 'Test publication'
},
stats: {
totalAmountOfMirrors: 5,
totalAmountOfComments: 3,
totalUpvotes: 10
}
} as unknown as AnyPublicationFragment;

const mockProfile = {
id: '0x01',
handle: 'test.lens',
stats: {
totalFollowers: 100,
totalFollowing: 50
}
} as unknown as ProfileFragment;

describe('createTestInteraction', () => {
it('should create mirror interaction when conditions are met', () => {
const interaction = createTestInteraction(mockPublication, mockProfile);
expect(interaction).toBeDefined();
if (interaction) {
expect(['MIRROR', 'COMMENT', 'LIKE', 'FOLLOW']).toContain(interaction.type);
}
});

it('should return null when no interaction is needed', () => {
const lowStatsPublication = {
...mockPublication,
stats: {
totalAmountOfMirrors: 0,
totalAmountOfComments: 0,
totalUpvotes: 0
}
} as unknown as AnyPublicationFragment;
const interaction = createTestInteraction(lowStatsPublication, mockProfile);
expect(interaction).toBeNull();
});
});

describe('handleTestInteraction', () => {
let client: LensClient;

beforeEach(() => {
vi.clearAllMocks();
client = new LensClient({
runtime: {
name: 'test-runtime',
memory: new Map(),
getMemory: vi.fn(),
setMemory: vi.fn(),
clearMemory: vi.fn()
},
cache: new Map(),
account: {
address: '0x123' as `0x${string}`,
privateKey: '0xabc' as `0x${string}`,
signMessage: vi.fn(),
signTypedData: vi.fn()
},
profileId: '0x01' as `0x${string}`
});
});

it('should handle mirror interaction successfully', async () => {
const interaction = {
type: 'MIRROR' as const,
publicationId: 'pub-1'
};

const result = await handleTestInteraction(client, interaction);
expect(result).toBeDefined();
expect(result.id).toBe('mirror-1');
expect(client.mirror).toHaveBeenCalledWith('pub-1');
});

it('should handle comment interaction successfully', async () => {
const interaction = {
type: 'COMMENT' as const,
publicationId: 'pub-1',
content: 'Test comment'
};

const result = await handleTestInteraction(client, interaction);
expect(result).toBeDefined();
expect(result.id).toBe('comment-1');
expect(client.comment).toHaveBeenCalledWith('pub-1', 'Test comment');
});

it('should handle interaction errors', async () => {
const interaction = {
type: 'MIRROR' as const,
publicationId: 'pub-1'
};

vi.mocked(client.mirror).mockRejectedValueOnce(new Error('Mirror failed'));
await expect(handleTestInteraction(client, interaction)).rejects.toThrow('Mirror failed');
});
});
});
64 changes: 64 additions & 0 deletions packages/client-lens/__tests__/post.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createTestPost } from './test-utils';
import { LensClient } from '../src/client';

// Mock dependencies
vi.mock('../src/client', () => ({
LensClient: vi.fn().mockImplementation(() => ({
authenticate: vi.fn().mockResolvedValue(undefined),
post: vi.fn().mockResolvedValue({ id: 'post-1' })
}))
}));

describe('Post Functions', () => {
let client: LensClient;

beforeEach(() => {
vi.clearAllMocks();
client = new LensClient({
runtime: {
name: 'test-runtime',
memory: new Map(),
getMemory: vi.fn(),
setMemory: vi.fn(),
clearMemory: vi.fn()
},
cache: new Map(),
account: {
address: '0x123' as `0x${string}`,
privateKey: '0xabc' as `0x${string}`,
signMessage: vi.fn(),
signTypedData: vi.fn()
},
profileId: '0x01' as `0x${string}`
});
});

describe('createTestPost', () => {
it('should create a post successfully', async () => {
const content = 'Test post content';
const result = await createTestPost(client, content);

expect(result).toBeDefined();
expect(result.id).toBe('post-1');
expect(client.post).toHaveBeenCalledWith(content);
});

it('should handle post creation errors', async () => {
const content = 'Test post content';
vi.mocked(client.post).mockRejectedValueOnce(new Error('Post creation failed'));

await expect(createTestPost(client, content)).rejects.toThrow('Post creation failed');
});

it('should handle empty content', async () => {
const content = '';
await expect(createTestPost(client, content)).rejects.toThrow('Post content cannot be empty');
});

it('should handle very long content', async () => {
const content = 'a'.repeat(5001); // Assuming max length is 5000
await expect(createTestPost(client, content)).rejects.toThrow('Post content too long');
});
});
});
63 changes: 63 additions & 0 deletions packages/client-lens/__tests__/test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { AnyPublicationFragment } from "@lens-protocol/client";
import type { LensClient } from "../src/client";
import type { Profile } from "../src/types";

export interface TestInteraction {
type: 'MIRROR' | 'COMMENT' | 'LIKE' | 'FOLLOW';
publicationId?: string;
content?: string;
}

export function createTestInteraction(publication: AnyPublicationFragment, profile: Profile): TestInteraction | null {
const stats = publication.stats;

// Simple heuristic: if the publication has good engagement, mirror it
if (stats.totalAmountOfMirrors > 3 || stats.totalAmountOfComments > 2 || stats.totalUpvotes > 5) {
return {
type: 'MIRROR',
publicationId: publication.id
};
}

// If the publication is engaging but not viral, comment on it
if (stats.totalAmountOfComments > 0 || stats.totalUpvotes > 2) {
return {
type: 'COMMENT',
publicationId: publication.id,
content: 'Interesting perspective!'
};
}

return null;
}

export async function handleTestInteraction(client: LensClient, interaction: TestInteraction) {
switch (interaction.type) {
case 'MIRROR':
if (!interaction.publicationId) throw new Error('Publication ID required for mirror');
return await client.mirror(interaction.publicationId);
case 'COMMENT':
if (!interaction.publicationId || !interaction.content) {
throw new Error('Publication ID and content required for comment');
}
return await client.comment(interaction.publicationId, interaction.content);
case 'LIKE':
if (!interaction.publicationId) throw new Error('Publication ID required for like');
return await client.like(interaction.publicationId);
case 'FOLLOW':
if (!interaction.publicationId) throw new Error('Profile ID required for follow');
return await client.follow(interaction.publicationId);
default:
throw new Error('Unknown interaction type');
}
}

export async function createTestPost(client: LensClient, content: string) {
if (!content) {
throw new Error('Post content cannot be empty');
}
if (content.length > 5000) {
throw new Error('Post content too long');
}
return await client.post(content);
}
Loading

0 comments on commit 3a69164

Please sign in to comment.