返回 AiToEarn
util.mcp.spec.ts
根目录 / project / aitoearn-backend / apps / aitoearn-ai / src / core / agent / mcp / util.mcp.spec.ts
1 import type { AiAvailabilityService } from '../../ai-availability'
2 import { Logger } from '@nestjs/common'
3 import { ContentGenerationTaskRepository } from '@yikart/mongodb'
4 import { vi } from 'vitest'
5 import { UtilMcp, UtilToolName } from './util.mcp'
6
7 describe('utilMcp', () => {
8 let utilMcp: UtilMcp
9 let mockLogger: Logger
10 let mockContentGenerateRepository: vi.Mocked<ContentGenerationTaskRepository>
11 let mockAiAvailability: vi.Mocked<Pick<AiAvailabilityService, 'execute'>>
12
13 beforeEach(() => {
14 mockLogger = {
15 debug: vi.fn(),
16 error: vi.fn(),
17 fatal: vi.fn(),
18 } as unknown as Logger
19
20 mockContentGenerateRepository = {
21 updateById: vi.fn().mockResolvedValue(undefined),
22 } as unknown as vi.Mocked<ContentGenerationTaskRepository>
23
24 mockAiAvailability = {
25 execute: vi.fn().mockImplementation((_ctx: unknown, fn: () => unknown) => (fn as () => Promise<unknown>)()),
26 } as unknown as vi.Mocked<Pick<AiAvailabilityService, 'execute'>>
27
28 utilMcp = new UtilMcp(mockContentGenerateRepository, mockAiAvailability as unknown as AiAvailabilityService)
29 // Override the logger for testing
30 Object.defineProperty(utilMcp, 'logger', { value: mockLogger })
31 })
32
33 describe('getCurrentTime', () => {
34 it('should return current time in ISO 8601 format', async () => {
35 const result = await utilMcp.getCurrentTime.handler({}, {})
36
37 expect(result.isError).toBeUndefined()
38 expect(result.content).toBeDefined()
39 expect(result.content[0]).toHaveProperty('type', 'text')
40
41 const textContent = result.content[0] as { type: 'text', text: string }
42 expect(textContent.text).toContain('Current time:')
43 expect(textContent.text).toContain('ISO 8601:')
44 // Verify it contains a valid ISO date format
45 expect(textContent.text).toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
46 })
47
48 it('should have correct tool name', () => {
49 expect(utilMcp.getCurrentTime.name).toBe(UtilToolName.GetCurrentTime)
50 })
51 })
52
53 describe('wait', () => {
54 beforeEach(() => {
55 vi.useFakeTimers()
56 })
57
58 afterEach(() => {
59 vi.useRealTimers()
60 })
61
62 it('should wait for specified seconds', async () => {
63 const waitPromise = utilMcp.wait.handler({ seconds: 5 }, {})
64
65 // Fast-forward time
66 vi.advanceTimersByTime(5000)
67
68 const result = await waitPromise
69
70 expect(result.isError).toBeUndefined()
71 expect(result.content).toBeDefined()
72 const textContent = result.content[0] as { type: 'text', text: string }
73 expect(textContent.text).toBe('Waited for 5 seconds')
74 })
75
76 it('should have correct tool name', () => {
77 expect(utilMcp.wait.name).toBe(UtilToolName.Wait)
78 })
79
80 it('should have correct description', () => {
81 expect(utilMcp.wait.description).toContain('Wait for a specified number of seconds')
82 })
83 })
84
85 describe('createSetTitleTool', () => {
86 const taskId = 'test-task-id'
87
88 it('should update title in repository', async () => {
89 const [setTitleTool, , cleanup] = utilMcp.createSetTitleTool(taskId)
90
91 const result = await setTitleTool.handler({ title: 'New Title' }, {})
92
93 expect(mockContentGenerateRepository.updateById).toHaveBeenCalledWith(taskId, {
94 title: 'New Title',
95 })
96 expect(result.isError).toBeUndefined()
97 const textContent = result.content[0] as { type: 'text', text: string }
98 expect(textContent.text).toBe('Title updated successfully')
99
100 cleanup()
101 })
102
103 it('should emit title update event via Subject', async () => {
104 const [setTitleTool, titleObservable, cleanup] = utilMcp.createSetTitleTool(taskId)
105
106 const emittedEvents: unknown[] = []
107 const subscription = titleObservable.subscribe(event => emittedEvents.push(event))
108
109 await setTitleTool.handler({ title: 'Test Title' }, {})
110
111 expect(emittedEvents).toHaveLength(1)
112 expect(emittedEvents[0]).toMatchObject({
113 taskId,
114 title: 'Test Title',
115 })
116
117 subscription.unsubscribe()
118 cleanup()
119 })
120
121 it('should have correct tool name', () => {
122 const [setTitleTool, , cleanup] = utilMcp.createSetTitleTool(taskId)
123
124 expect(setTitleTool.name).toBe(UtilToolName.SetTitle)
125
126 cleanup()
127 })
128
129 it('should complete subject on cleanup', () => {
130 const [, titleObservable, cleanup] = utilMcp.createSetTitleTool(taskId)
131
132 let completed = false
133 titleObservable.subscribe({
134 complete: () => {
135 completed = true
136 },
137 })
138
139 cleanup()
140
141 expect(completed).toBe(true)
142 })
143 })
144
145 describe('server', () => {
146 it('should create server with correct name', () => {
147 expect(utilMcp.server).toBeDefined()
148 expect(utilMcp.server.name).toBe('util')
149 })
150
151 it('should include wait and getCurrentTime tools', () => {
152 const server = utilMcp.server as { tools?: Array<{ name: string }> }
153 const tools = server.tools
154 expect(tools).toBeDefined()
155 expect(tools?.length).toBeGreaterThanOrEqual(2)
156
157 const toolNames = tools?.map(t => t.name)
158 expect(toolNames).toContain(UtilToolName.Wait)
159 expect(toolNames).toContain(UtilToolName.GetCurrentTime)
160 })
161 })
162 })
163
163 lines TYPESCRIPT