返回 AiToEarn
channels.mcp.service.spec.ts
根目录 / project / aitoearn-backend / apps / aitoearn-server / src / core / channels / mcp / channels.mcp.service.spec.ts
1 import { AccountType } from '@yikart/common'
2 import { PublishRecordSource } from '@yikart/mongodb'
3 import { describe, expect, it, vi } from 'vitest'
4 import { ChannelsMcpService } from './channels.mcp.service'
5
6 vi.mock('@yikart/mongodb', () => ({
7 PublishRecordSource: {
8 Mcp: 'mcp',
9 },
10 }))
11
12 vi.mock('../analytics/analytics.service', () => ({ AnalyticsService: class AnalyticsService {} }))
13 vi.mock('../engagement/engagement.service', () => ({ EngagementService: class EngagementService {} }))
14 vi.mock('../platforms/platforms.service', () => ({ PlatformsService: class PlatformsService {} }))
15 vi.mock('../platforms/platforms.registry', () => ({ PlatformIntegrationRegistry: class PlatformIntegrationRegistry {} }))
16 vi.mock('../publish/flows/publish-flow.service', () => ({ PublishFlowService: class PublishFlowService {} }))
17 vi.mock('../publish/records/publish-record-read.service', () => ({ PublishRecordReadService: class PublishRecordReadService {} }))
18 vi.mock('../publish/tasks/publish-task.service', () => ({ PublishTaskService: class PublishTaskService {} }))
19 vi.mock('../works/work.service', () => ({ WorkService: class WorkService {} }))
20
21 function createService(overrides: {
22 publishFlowService?: Record<string, unknown>
23 publishTaskService?: Record<string, unknown>
24 recordReadService?: Record<string, unknown>
25 analyticsService?: Record<string, unknown>
26 engagementService?: Record<string, unknown>
27 workService?: Record<string, unknown>
28 registry?: Record<string, unknown>
29 platformsService?: Record<string, unknown>
30 } = {}) {
31 return new ChannelsMcpService(
32 (overrides.publishFlowService ?? {}) as never,
33 (overrides.publishTaskService ?? {}) as never,
34 (overrides.recordReadService ?? {}) as never,
35 (overrides.analyticsService ?? {}) as never,
36 (overrides.engagementService ?? {}) as never,
37 (overrides.workService ?? {}) as never,
38 (overrides.registry ?? {}) as never,
39 (overrides.platformsService ?? {}) as never,
40 )
41 }
42
43 describe('channelsMcpService', () => {
44 it('marks publish flows as MCP source', async () => {
45 const publishFlowService = {
46 createFlow: vi.fn().mockResolvedValue({ flowId: 'flow_1', tasks: [] }),
47 }
48 const service = createService({ publishFlowService })
49
50 await service.createChannelPublishFlow('user_1', {
51 content: {
52 title: 'title',
53 body: 'body',
54 media: [],
55 topics: [],
56 },
57 publishAt: new Date('2026-05-25T00:00:00.000Z'),
58 context: {
59 materialId: 'material_1',
60 },
61 items: [{
62 platform: AccountType.Twitter,
63 accountId: 'account_1',
64 }],
65 })
66
67 expect(publishFlowService.createFlow).toHaveBeenCalledWith('user_1', expect.objectContaining({
68 context: expect.objectContaining({
69 materialId: 'material_1',
70 source: PublishRecordSource.Mcp,
71 }),
72 }))
73 })
74
75 it('reads publish records by flowId without a union lookup payload', async () => {
76 const recordReadService = {
77 getByFlowId: vi.fn().mockResolvedValue({ id: 'record_1' }),
78 getDetail: vi.fn(),
79 }
80 const service = createService({ recordReadService })
81
82 await expect(service.getChannelPublishRecordByFlowId('user_1', {
83 flowId: 'flow_1',
84 })).resolves.toEqual({ id: 'record_1' })
85
86 expect(recordReadService.getByFlowId).toHaveBeenCalledWith('user_1', 'flow_1')
87 expect(recordReadService.getDetail).not.toHaveBeenCalled()
88 })
89
90 it('reads publish records by recordId without a union lookup payload', async () => {
91 const recordReadService = {
92 getDetail: vi.fn().mockResolvedValue({ id: 'record_1' }),
93 }
94 const service = createService({ recordReadService })
95
96 await expect(service.getChannelPublishRecordByRecordId('user_1', {
97 recordId: 'record_1',
98 })).resolves.toEqual({ id: 'record_1' })
99
100 expect(recordReadService.getDetail).toHaveBeenCalledWith('record_1', 'user_1')
101 })
102
103 it('reads publish records by taskId without using record detail lookup', async () => {
104 const recordReadService = {
105 getByTaskId: vi.fn().mockResolvedValue({ id: 'record_1', taskId: 'task_1' }),
106 getDetail: vi.fn(),
107 }
108 const service = createService({ recordReadService })
109
110 await expect(service.getChannelPublishRecordByTaskId('user_1', {
111 taskId: 'task_1',
112 })).resolves.toEqual({ id: 'record_1', taskId: 'task_1' })
113
114 expect(recordReadService.getByTaskId).toHaveBeenCalledWith('user_1', 'task_1')
115 expect(recordReadService.getDetail).not.toHaveBeenCalled()
116 })
117
118 it('keeps taskId publish record misses as nullable MCP results', async () => {
119 const recordReadService = {
120 getByTaskId: vi.fn().mockResolvedValue(null),
121 }
122 const service = createService({ recordReadService })
123
124 await expect(service.getChannelPublishRecordByTaskId('user_1', {
125 taskId: 'task_1',
126 })).resolves.toBeNull()
127
128 expect(recordReadService.getByTaskId).toHaveBeenCalledWith('user_1', 'task_1')
129 })
130
131 it('uses account-scoped option sources for YouTube categories', async () => {
132 const publishOptions = {
133 getAccountValues: vi.fn().mockResolvedValue({ field: 'categoryId', items: [] }),
134 }
135 const service = createService({ platformsService: publishOptions })
136
137 await service.listYoutubeChannelPlatformCategories('user_1', {
138 accountId: 'account_1',
139 regionCode: 'US',
140 })
141
142 expect(publishOptions.getAccountValues).toHaveBeenCalledWith('user_1', 'account_1', 'categoryId', {
143 id: undefined,
144 regionCode: 'US',
145 })
146 })
147
148 it('uses account-scoped option sources for Bilibili categories', async () => {
149 const publishOptions = {
150 getAccountValues: vi.fn().mockResolvedValue({ field: 'tid', items: [] }),
151 }
152 const service = createService({ platformsService: publishOptions })
153
154 await service.listBilibiliChannelPlatformCategories('user_1', {
155 accountId: 'account_1',
156 })
157
158 expect(publishOptions.getAccountValues).toHaveBeenCalledWith('user_1', 'account_1', 'tid')
159 })
160 })
161
161 lines TYPESCRIPT