- Created a new test file `channels.test.ts` to cover utilities related to channel configurations and targets. - Implemented tests for normalizing and grouping selected channels by type, as well as building channel targets from account data and cron history. - Mocked necessary dependencies to isolate tests and ensure accurate results. - Updated `vite.config.ts` to set up the testing environment with jsdom and enable global variables for tests.
288 lines
7.4 KiB
TypeScript
288 lines
7.4 KiB
TypeScript
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',
|
|
});
|
|
});
|
|
});
|