import { beforeEach, describe, expect, it, vi } from 'vitest'; const mocks = vi.hoisted(() => ({ configGet: vi.fn(), listStoredChannelAccountRecords: vi.fn(), listCronJobs: vi.fn(), })); vi.mock('@electron/service/config-service', () => ({ default: { get: mocks.configGet, }, configManager: { get: mocks.configGet, }, })); vi.mock('../electron/utils/cron-store', () => ({ listCronJobs: mocks.listCronJobs, })); vi.mock('../electron/utils/channel-config', () => ({ listStoredChannelAccountRecords: mocks.listStoredChannelAccountRecords, })); import { getSelectedChannelsConfig, listSelectedChannelAccountGroups, listSelectedChannelTargets, } from '../electron/utils/channels'; describe('channels utilities', () => { beforeEach(() => { mocks.configGet.mockReset(); mocks.listStoredChannelAccountRecords.mockReset(); mocks.listCronJobs.mockReset(); }); it('normalizes and groups selected channels by inferred channel type', () => { mocks.configGet.mockReturnValue([ { id: ' acct-1 ', channelName: ' 抖音 ', channelUrl: ' https://life.douyin.com/live ', }, { id: 'acct-1', channelName: '抖音直播', channelUrl: 'https://douyin.com/duplicate', }, { id: 'alpha', channelName: 'Alpha Store', channelUrl: 'https://meituan.com/a', }, { id: 'beta', channelName: 'Beta Store', channelUrl: 'https://me.meituan.com/b', }, ]); expect(getSelectedChannelsConfig()).toEqual([ { id: 'acct-1', channelName: '抖音', channelUrl: 'https://life.douyin.com/live', }, { id: 'alpha', channelName: 'Alpha Store', channelUrl: 'https://meituan.com/a', }, { id: 'beta', channelName: 'Beta Store', channelUrl: 'https://me.meituan.com/b', }, ]); mocks.listStoredChannelAccountRecords.mockReturnValue([ { channelType: 'douyin', channelLabel: '抖音', defaultAccountId: 'acct-1', channelEnabled: true, accountId: 'acct-1', accountName: '抖音', accountEnabled: true, channelUrl: 'https://life.douyin.com/live', config: {}, metadata: {}, }, { channelType: 'meituan', channelLabel: 'Alpha Store', defaultAccountId: 'alpha', channelEnabled: true, accountId: 'alpha', accountName: 'Alpha Store', accountEnabled: true, channelUrl: 'https://meituan.com/a', config: {}, metadata: {}, }, { channelType: 'meituan', channelLabel: 'Alpha Store', defaultAccountId: 'alpha', channelEnabled: true, accountId: 'beta', accountName: 'Beta Store', accountEnabled: true, channelUrl: 'https://me.meituan.com/b', config: {}, metadata: {}, }, ]); const groups = listSelectedChannelAccountGroups({ agents: [ { id: 'Agent-A', name: 'Ada' }, { id: 'agent-b', name: 'Ben' }, ], channelOwners: { meituan: 'Agent-A', }, channelAccountOwners: { 'meituan:alpha': 'agent-b', }, }); const byType = Object.fromEntries(groups.map((group) => [group.channelType, group])); expect(byType.douyin).toMatchObject({ channelType: 'douyin', channelLabel: '抖音', defaultAccountId: 'acct-1', status: 'degraded', accounts: [ { accountId: 'acct-1', name: '抖音', configured: true, status: 'degraded', isDefault: true, channelUrl: 'https://life.douyin.com/live', }, ], }); expect(byType.meituan).toMatchObject({ channelType: 'meituan', channelLabel: 'Alpha Store', defaultAccountId: 'alpha', status: 'connected', accounts: [ { accountId: 'alpha', name: 'Alpha Store', configured: true, status: 'connected', isDefault: true, agentId: 'agent-b', bindingScope: 'account', channelUrl: 'https://meituan.com/a', }, { accountId: 'beta', name: 'Beta Store', configured: true, status: 'connected', isDefault: false, agentId: 'agent-a', bindingScope: 'channel', channelUrl: 'https://me.meituan.com/b', }, ], }); }); it('builds channel targets from account data, URL hints, and cron history', () => { mocks.listStoredChannelAccountRecords.mockReturnValue([ { id: 'acct-1', channelType: 'douyin', channelLabel: '抖音直播间', defaultAccountId: 'acct-1', channelEnabled: true, accountId: 'acct-1', accountName: '抖音直播间', accountEnabled: true, channelUrl: 'https://webhook.example.com/send?roomId=ROOM-9#panel?threadId=TH-1', config: {}, metadata: {}, }, ]); mocks.listCronJobs.mockReturnValue([ { id: 'cron-1', name: 'nightly announce', message: 'hello', schedule: '* * * * *', enabled: true, createdAt: '2026-04-18T00:00:00.000Z', updatedAt: '2026-04-18T00:00:00.000Z', delivery: { mode: 'announce', channel: 'douyin', accountId: 'acct-1', to: 'https://history.example.com/announce', }, }, { id: 'cron-2', name: 'ignored', message: 'skip me', schedule: '* * * * *', enabled: true, createdAt: '2026-04-18T00:00:00.000Z', updatedAt: '2026-04-18T00:00:00.000Z', delivery: { mode: 'announce', channel: 'fliggy', to: 'should-not-appear', }, }, ]); const targets = listSelectedChannelTargets('douyin', 'acct-1'); expect(targets[0]).toMatchObject({ value: '抖音直播间', kind: 'name', source: 'channel-name', }); expect(targets).toContainEqual(expect.objectContaining({ value: 'acct-1', label: '账号 ID · acct-1', kind: 'identifier', source: 'account-id', channelType: 'douyin', accountId: 'acct-1', })); expect(targets).toContainEqual(expect.objectContaining({ value: 'ROOM-9', label: 'Room ID · ROOM-9', kind: 'identifier', source: 'query-param', channelType: 'douyin', accountId: 'acct-1', })); expect(targets).toContainEqual(expect.objectContaining({ value: 'TH-1', label: 'Thread ID · TH-1', kind: 'identifier', source: 'hash-param', channelType: 'douyin', accountId: 'acct-1', })); expect(targets).toContainEqual(expect.objectContaining({ kind: 'webhook', source: 'channel-url', channelType: 'douyin', accountId: 'acct-1', })); expect(targets).toContainEqual(expect.objectContaining({ value: 'https://history.example.com/announce', kind: 'webhook', source: 'fallback', channelType: 'douyin', accountId: 'acct-1', })); const filtered = listSelectedChannelTargets('douyin', 'acct-1', 'history'); expect(filtered).toHaveLength(1); expect(filtered[0]).toMatchObject({ value: 'https://history.example.com/announce', }); }); });