Skip to content

Commit 9d83ba9

Browse files
authored
Merge branch 'develop' into test/db-adapters
2 parents c5c2ad4 + 06bd24e commit 9d83ba9

File tree

8 files changed

+554
-78
lines changed

8 files changed

+554
-78
lines changed

.github/workflows/smoke-tests.yml

+14-5
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,26 @@ on:
1010
jobs:
1111
smoke-tests:
1212
runs-on: ubuntu-latest
13+
container:
14+
image: node:23-bullseye
15+
env:
16+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
1317
steps:
1418
- uses: actions/checkout@v4
1519

16-
- uses: pnpm/action-setup@v3
20+
- name: Cache pnpm
21+
uses: actions/cache@v4
1722
with:
18-
version: 9.15.0
23+
path: |
24+
~/.pnpm-store
25+
**/node_modules
26+
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
27+
restore-keys: ${{ runner.os }}-pnpm-
1928

20-
- uses: actions/setup-node@v4
29+
- name: Setup pnpm
30+
uses: pnpm/action-setup@v3
2131
with:
22-
node-version: "23.3.0"
23-
cache: "pnpm"
32+
version: 9.15.0
2433

2534
- name: Run smoke tests
2635
run: pnpm run smokeTests

client/src/components/chat.tsx

+18-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from "@/components/ui/chat/chat-bubble";
77
import { ChatInput } from "@/components/ui/chat/chat-input";
88
import { ChatMessageList } from "@/components/ui/chat/chat-message-list";
9-
import { useTransition, animated } from "@react-spring/web";
9+
import { useTransition, animated, AnimatedProps } from "@react-spring/web";
1010
import { Paperclip, Send, X } from "lucide-react";
1111
import { useEffect, useRef, useState } from "react";
1212
import { Content, UUID } from "@elizaos/core";
@@ -23,14 +23,18 @@ import { IAttachment } from "@/types";
2323
import { AudioRecorder } from "./audio-recorder";
2424
import { Badge } from "./ui/badge";
2525

26-
interface ExtraContentFields {
26+
type ExtraContentFields = {
2727
user: string;
2828
createdAt: number;
2929
isLoading?: boolean;
30-
}
30+
};
3131

3232
type ContentWithUser = Content & ExtraContentFields;
3333

34+
type AnimatedDivProps = AnimatedProps<{ style: React.CSSProperties }> & {
35+
children?: React.ReactNode;
36+
};
37+
3438
export default function Page({ agentId }: { agentId: UUID }) {
3539
const { toast } = useToast();
3640
const [selectedFile, setSelectedFile] = useState<File | null>(null);
@@ -67,7 +71,6 @@ export default function Page({ agentId }: { agentId: UUID }) {
6771
}
6872
};
6973

70-
7174
const handleSendMessage = (e: React.FormEvent<HTMLFormElement>) => {
7275
e.preventDefault();
7376
if (!input) return;
@@ -167,17 +170,23 @@ export default function Page({ agentId }: { agentId: UUID }) {
167170
leave: { opacity: 0, transform: "translateY(10px)" },
168171
});
169172

173+
const CustomAnimatedDiv = animated.div as React.FC<AnimatedDivProps>;
174+
170175
return (
171176
<div className="flex flex-col w-full h-[calc(100dvh)] p-4">
172177
<div className="flex-1 overflow-y-auto">
173178
<ChatMessageList ref={messagesContainerRef}>
174179
{transitions((styles, message) => {
175180
const variant = getMessageVariant(message?.user);
176181
return (
177-
// @ts-expect-error
178-
<animated.div
179-
style={styles}
180-
className="flex flex-col gap-2 p-4"
182+
<CustomAnimatedDiv
183+
style={{
184+
...styles,
185+
display: "flex",
186+
flexDirection: "column",
187+
gap: "0.5rem",
188+
padding: "1rem",
189+
}}
181190
>
182191
<ChatBubble
183192
variant={variant}
@@ -266,7 +275,7 @@ export default function Page({ agentId }: { agentId: UUID }) {
266275
</div>
267276
</div>
268277
</ChatBubble>
269-
</animated.div>
278+
</CustomAnimatedDiv>
270279
);
271280
})}
272281
</ChatMessageList>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2+
import { RedisClient } from '../src';
3+
import { type UUID, elizaLogger } from '@elizaos/core';
4+
import Redis from 'ioredis';
5+
6+
// Mock ioredis
7+
vi.mock('ioredis', () => {
8+
const MockRedis = vi.fn(() => ({
9+
on: vi.fn(),
10+
get: vi.fn(),
11+
set: vi.fn(),
12+
del: vi.fn(),
13+
quit: vi.fn()
14+
}));
15+
return { default: MockRedis };
16+
});
17+
18+
// Mock elizaLogger
19+
vi.mock('@elizaos/core', async () => {
20+
const actual = await vi.importActual('@elizaos/core');
21+
return {
22+
...actual as any,
23+
elizaLogger: {
24+
success: vi.fn(),
25+
error: vi.fn()
26+
}
27+
};
28+
});
29+
30+
describe('RedisClient', () => {
31+
let client: RedisClient;
32+
let mockRedis: any;
33+
34+
beforeEach(() => {
35+
vi.clearAllMocks();
36+
client = new RedisClient('redis://localhost:6379');
37+
// Get the instance created by the constructor
38+
mockRedis = (Redis as unknown as ReturnType<typeof vi.fn>).mock.results[0].value;
39+
});
40+
41+
afterEach(() => {
42+
vi.clearAllMocks();
43+
});
44+
45+
describe('constructor', () => {
46+
it('should set up event handlers', () => {
47+
expect(mockRedis.on).toHaveBeenCalledWith('connect', expect.any(Function));
48+
expect(mockRedis.on).toHaveBeenCalledWith('error', expect.any(Function));
49+
});
50+
51+
it('should log success on connect', () => {
52+
const connectHandler = mockRedis.on.mock.calls.find(call => call[0] === 'connect')[1];
53+
connectHandler();
54+
expect(elizaLogger.success).toHaveBeenCalledWith('Connected to Redis');
55+
});
56+
57+
it('should log error on error event', () => {
58+
const error = new Error('Redis connection error');
59+
const errorHandler = mockRedis.on.mock.calls.find(call => call[0] === 'error')[1];
60+
errorHandler(error);
61+
expect(elizaLogger.error).toHaveBeenCalledWith('Redis error:', error);
62+
});
63+
});
64+
65+
describe('getCache', () => {
66+
const agentId = 'test-agent' as UUID;
67+
const key = 'test-key';
68+
const expectedRedisKey = `${agentId}:${key}`;
69+
70+
it('should return cached value when it exists', async () => {
71+
const cachedValue = 'cached-data';
72+
mockRedis.get.mockResolvedValueOnce(cachedValue);
73+
74+
const result = await client.getCache({ agentId, key });
75+
76+
expect(mockRedis.get).toHaveBeenCalledWith(expectedRedisKey);
77+
expect(result).toBe(cachedValue);
78+
});
79+
80+
it('should return undefined when key does not exist', async () => {
81+
mockRedis.get.mockResolvedValueOnce(null);
82+
83+
const result = await client.getCache({ agentId, key });
84+
85+
expect(mockRedis.get).toHaveBeenCalledWith(expectedRedisKey);
86+
expect(result).toBeUndefined();
87+
});
88+
89+
it('should handle errors and return undefined', async () => {
90+
const error = new Error('Redis error');
91+
mockRedis.get.mockRejectedValueOnce(error);
92+
93+
const result = await client.getCache({ agentId, key });
94+
95+
expect(mockRedis.get).toHaveBeenCalledWith(expectedRedisKey);
96+
expect(elizaLogger.error).toHaveBeenCalledWith('Error getting cache:', error);
97+
expect(result).toBeUndefined();
98+
});
99+
});
100+
101+
describe('setCache', () => {
102+
const agentId = 'test-agent' as UUID;
103+
const key = 'test-key';
104+
const value = 'test-value';
105+
const expectedRedisKey = `${agentId}:${key}`;
106+
107+
it('should successfully set cache value', async () => {
108+
mockRedis.set.mockResolvedValueOnce('OK');
109+
110+
const result = await client.setCache({ agentId, key, value });
111+
112+
expect(mockRedis.set).toHaveBeenCalledWith(expectedRedisKey, value);
113+
expect(result).toBe(true);
114+
});
115+
116+
it('should handle errors and return false', async () => {
117+
const error = new Error('Redis error');
118+
mockRedis.set.mockRejectedValueOnce(error);
119+
120+
const result = await client.setCache({ agentId, key, value });
121+
122+
expect(mockRedis.set).toHaveBeenCalledWith(expectedRedisKey, value);
123+
expect(elizaLogger.error).toHaveBeenCalledWith('Error setting cache:', error);
124+
expect(result).toBe(false);
125+
});
126+
});
127+
128+
describe('deleteCache', () => {
129+
const agentId = 'test-agent' as UUID;
130+
const key = 'test-key';
131+
const expectedRedisKey = `${agentId}:${key}`;
132+
133+
it('should successfully delete cache when key exists', async () => {
134+
mockRedis.del.mockResolvedValueOnce(1);
135+
136+
const result = await client.deleteCache({ agentId, key });
137+
138+
expect(mockRedis.del).toHaveBeenCalledWith(expectedRedisKey);
139+
expect(result).toBe(true);
140+
});
141+
142+
it('should return false when key does not exist', async () => {
143+
mockRedis.del.mockResolvedValueOnce(0);
144+
145+
const result = await client.deleteCache({ agentId, key });
146+
147+
expect(mockRedis.del).toHaveBeenCalledWith(expectedRedisKey);
148+
expect(result).toBe(false);
149+
});
150+
151+
it('should handle errors and return false', async () => {
152+
const error = new Error('Redis error');
153+
mockRedis.del.mockRejectedValueOnce(error);
154+
155+
const result = await client.deleteCache({ agentId, key });
156+
157+
expect(mockRedis.del).toHaveBeenCalledWith(expectedRedisKey);
158+
expect(elizaLogger.error).toHaveBeenCalledWith('Error deleting cache:', error);
159+
expect(result).toBe(false);
160+
});
161+
});
162+
163+
describe('disconnect', () => {
164+
it('should successfully disconnect from Redis', async () => {
165+
mockRedis.quit.mockResolvedValueOnce('OK');
166+
167+
await client.disconnect();
168+
169+
expect(mockRedis.quit).toHaveBeenCalled();
170+
expect(elizaLogger.success).toHaveBeenCalledWith('Disconnected from Redis');
171+
});
172+
173+
it('should handle disconnect errors', async () => {
174+
const error = new Error('Redis disconnect error');
175+
mockRedis.quit.mockRejectedValueOnce(error);
176+
177+
await client.disconnect();
178+
179+
expect(mockRedis.quit).toHaveBeenCalled();
180+
expect(elizaLogger.error).toHaveBeenCalledWith('Error disconnecting from Redis:', error);
181+
});
182+
});
183+
});

packages/adapter-redis/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@
2424
},
2525
"devDependencies": {
2626
"@types/ioredis": "^5.0.0",
27-
"tsup": "8.3.5"
27+
"tsup": "8.3.5",
28+
"vitest": "^3.0.2"
2829
},
2930
"scripts": {
3031
"build": "tsup --format esm --dts",
3132
"dev": "tsup --format esm --dts --watch",
32-
"lint": "eslint --fix --cache ."
33+
"lint": "eslint --fix --cache .",
34+
"test": "vitest run",
35+
"test:watch": "vitest"
3336
},
3437
"peerDependencies": {
3538
"whatwg-url": "7.1.0"
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig } from 'vitest/config';
2+
3+
export default defineConfig({
4+
test: {
5+
globals: true,
6+
environment: 'node',
7+
},
8+
});

0 commit comments

Comments
 (0)