返回 AiToEarn
channels.mcp.controller.spec.ts
根目录 / project / aitoearn-backend / apps / aitoearn-server / src / core / channels / mcp / channels.mcp.controller.spec.ts
1 import { AccountType } from '@yikart/common'
2 import { PublishStatus, PublishType } from '@yikart/mongodb'
3 import { beforeEach, describe, expect, it, vi } from 'vitest'
4 import { ChannelEngagementActionType, ChannelEngagementFunctionName, ChannelEngagementTargetType, ChannelPaginationMode } from '../platforms/platforms.interface'
5 import { ChannelsMcpController } from './channels.mcp.controller'
6
7 const commonMocks = vi.hoisted(() => ({
8 getUser: vi.fn(() => ({ id: 'user-1' })),
9 toYamlTextResult: vi.fn((value: unknown) => value),
10 }))
11
12 vi.mock('@yikart/common', async (importOriginal) => {
13 const actual = await importOriginal<typeof import('@yikart/common')>()
14 return {
15 ...actual,
16 getUser: commonMocks.getUser,
17 toYamlTextResult: commonMocks.toYamlTextResult,
18 }
19 })
20
21 vi.mock('@yikart/nest-mcp', () => ({
22 Tool: () => () => undefined,
23 }))
24
25 vi.mock('@yikart/mongodb', () => ({
26 PublishRecordSource: {
27 Mcp: 'mcp',
28 Web: 'web',
29 },
30 PublishRecordLinkStatus: {
31 PENDING: 'pending',
32 READY: 'ready',
33 FAILED: 'failed',
34 },
35 PublishStatus: {
36 Failed: -1,
37 WaitingForPublish: 0,
38 Published: 1,
39 Publishing: 2,
40 PlatformScheduled: 7,
41 WaitingForUserAction: 8,
42 Canceled: 9,
43 },
44 PublishType: {
45 VIDEO: 'video',
46 ARTICLE: 'article',
47 },
48 }))
49
50 vi.mock('./channels.mcp.service', () => ({
51 ChannelsMcpService: class ChannelsMcpService {},
52 }))
53
54 describe('channelsMcpController', () => {
55 beforeEach(() => {
56 commonMocks.getUser.mockClear()
57 commonMocks.toYamlTextResult.mockClear()
58 })
59
60 it('serializes a missing taskId publish record as null', async () => {
61 const service = {
62 getChannelPublishRecordByTaskId: vi.fn(async () => null),
63 }
64 const controller = new ChannelsMcpController(service as never)
65
66 await controller.getChannelPublishRecordByTaskId({ taskId: 'task-1' })
67
68 expect(service.getChannelPublishRecordByTaskId).toHaveBeenCalledWith('user-1', { taskId: 'task-1' })
69 expect(commonMocks.toYamlTextResult).toHaveBeenCalledWith(null)
70 })
71
72 it('filters work detail output through the work VO before YAML serialization', async () => {
73 const fetchedAt = new Date('2026-01-01T00:00:00.000Z')
74 const controller = new ChannelsMcpController({
75 getChannelWorkDetail: vi.fn(async () => ({
76 platform: AccountType.YouTube,
77 work: {
78 id: 'video-1',
79 url: 'https://youtube.com/watch?v=video-1',
80 },
81 snapshots: [{
82 platformWorkId: 'video-1',
83 snapshotAt: fetchedAt,
84 fetchedAt,
85 work: { id: 'video-1' },
86 rawResponse: { token: 'snapshot-raw-token' },
87 }],
88 rawResponse: { token: 'raw-token' },
89 token: 'access-token',
90 })),
91 } as never)
92
93 await controller.getChannelWorkDetail({
94 platform: AccountType.YouTube,
95 platformWorkId: 'video-1',
96 accountId: 'account-1',
97 })
98
99 expect(commonMocks.toYamlTextResult).toHaveBeenCalledWith({
100 platform: AccountType.YouTube,
101 work: {
102 id: 'video-1',
103 url: 'https://youtube.com/watch?v=video-1',
104 },
105 snapshots: [{
106 platformWorkId: 'video-1',
107 snapshotAt: fetchedAt,
108 fetchedAt,
109 work: { id: 'video-1' },
110 }],
111 })
112 })
113
114 it('filters account analytics output through the analytics VO before YAML serialization', async () => {
115 const fetchedAt = new Date('2026-01-01T00:00:00.000Z')
116 const controller = new ChannelsMcpController({
117 getChannelAccountAnalytics: vi.fn(async () => ({
118 platform: AccountType.TikTok,
119 accountId: 'account-1',
120 snapshots: [{
121 platformUid: 'uid-1',
122 snapshotAt: fetchedAt,
123 fetchedAt,
124 metrics: { fansCount: 12 },
125 rawResponse: { token: 'snapshot-raw-token' },
126 }],
127 raw: { token: 'raw-token' },
128 errorData: { token: 'error-token' },
129 })),
130 } as never)
131
132 await controller.getChannelAccountAnalytics({
133 accountId: 'account-1',
134 })
135
136 expect(commonMocks.toYamlTextResult).toHaveBeenCalledWith({
137 platform: AccountType.TikTok,
138 accountId: 'account-1',
139 snapshots: [{
140 platformUid: 'uid-1',
141 snapshotAt: fetchedAt,
142 fetchedAt,
143 metrics: { fansCount: 12 },
144 }],
145 })
146 })
147
148 it('filters comments output through the comment list VO before YAML serialization', async () => {
149 const controller = new ChannelsMcpController({
150 listChannelEngagementComments: vi.fn(async () => ({
151 platform: AccountType.Facebook,
152 items: [{
153 platformCommentId: 'comment-1',
154 content: 'hello',
155 rawResponse: { token: 'raw-token' },
156 }],
157 pagination: { mode: ChannelPaginationMode.None },
158 token: 'access-token',
159 })),
160 } as never)
161
162 await controller.listChannelEngagementComments({
163 platform: AccountType.Facebook,
164 platformWorkId: 'post-1',
165 accountId: 'account-1',
166 pagination: {},
167 })
168
169 expect(commonMocks.toYamlTextResult).toHaveBeenCalledWith({
170 platform: AccountType.Facebook,
171 items: [{
172 platformCommentId: 'comment-1',
173 content: 'hello',
174 }],
175 pagination: { mode: ChannelPaginationMode.None },
176 })
177 })
178
179 it('filters publish record list output through the publish record VO before YAML serialization', async () => {
180 const publishTime = new Date('2026-01-01T00:00:00.000Z')
181 const controller = new ChannelsMcpController({
182 listChannelPublishRecords: vi.fn(async () => [{
183 id: 'record-1',
184 taskId: 'task-1',
185 accountType: AccountType.YouTube,
186 type: PublishType.VIDEO,
187 status: PublishStatus.Published,
188 publishTime,
189 errorData: {
190 type: 'platform',
191 code: '15070',
192 message: 'YouTube platform API request failed',
193 originalData: { raw: { token: 'error-token' } },
194 },
195 option: { privacyStatus: 'private' },
196 dataOption: { token: 'data-token' },
197 pendingUpdate: { title: 'draft' },
198 pendingMediaJobs: [{ id: 'job-1' }],
199 }]),
200 } as never)
201
202 await controller.listChannelPublishRecords({
203 accountType: AccountType.YouTube,
204 })
205
206 expect(commonMocks.toYamlTextResult).toHaveBeenCalledWith([{
207 id: 'record-1',
208 taskId: 'task-1',
209 accountType: AccountType.YouTube,
210 type: PublishType.VIDEO,
211 status: PublishStatus.Published,
212 publishTime,
213 errorData: {
214 type: 'platform',
215 code: '15070',
216 message: 'YouTube platform API request failed',
217 },
218 }])
219 })
220
221 it('filters engagement action output through the action VO before YAML serialization', async () => {
222 const controller = new ChannelsMcpController({
223 callChannelEngagementFunction: vi.fn(async () => ({
224 platform: AccountType.Twitter,
225 actionType: ChannelEngagementActionType.Like,
226 targetType: ChannelEngagementTargetType.Work,
227 targetId: 'tweet-1',
228 success: true,
229 rawResponse: { token: 'raw-token' },
230 accessToken: 'access-token',
231 })),
232 } as never)
233
234 await controller.callChannelEngagementFunction({
235 platform: AccountType.Twitter,
236 accountId: 'account-1',
237 name: ChannelEngagementFunctionName.Like,
238 data: { platformWorkId: 'tweet-1' },
239 })
240
241 expect(commonMocks.toYamlTextResult).toHaveBeenCalledWith({
242 platform: AccountType.Twitter,
243 actionType: ChannelEngagementActionType.Like,
244 targetType: ChannelEngagementTargetType.Work,
245 targetId: 'tweet-1',
246 success: true,
247 })
248 })
249 })
250
250 lines TYPESCRIPT