Add unit tests for channel utilities and configure testing environment
- 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.
This commit is contained in:
@@ -3,9 +3,20 @@ import type { NormalizedHostApiRequest } from '../route-utils';
|
||||
import { fail, ok, parseJsonBody } from '../route-utils';
|
||||
import {
|
||||
assignChannelToAgent,
|
||||
clearAllChannelBindings,
|
||||
clearChannelBinding,
|
||||
listAgentsSnapshot,
|
||||
} from '../../utils/agent-config';
|
||||
import {
|
||||
getChannelFormValues,
|
||||
hasStoredChannelAccount,
|
||||
isCanonicalChannelAccountId,
|
||||
listStoredChannelTypes,
|
||||
saveChannelConfig,
|
||||
setChannelDefaultAccount,
|
||||
setChannelEnabled,
|
||||
deleteChannelConfig,
|
||||
} from '../../utils/channel-config';
|
||||
import {
|
||||
listSelectedChannelAccountGroups,
|
||||
listSelectedChannelTargets,
|
||||
@@ -34,6 +45,13 @@ export async function handleChannelRoutes(
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname === '/api/channels/configured' && method === 'GET') {
|
||||
return ok({
|
||||
success: true,
|
||||
channels: listStoredChannelTypes(),
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname === '/api/channels/targets' && method === 'GET') {
|
||||
const channelType = request.url.searchParams.get('channelType')?.trim() || '';
|
||||
const accountId = request.url.searchParams.get('accountId')?.trim() || null;
|
||||
@@ -49,6 +67,67 @@ export async function handleChannelRoutes(
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname === '/api/channels/default-account' && method === 'PUT') {
|
||||
try {
|
||||
const body = parseJsonBody<{
|
||||
channelType?: string;
|
||||
accountId?: string | null;
|
||||
}>(request.body);
|
||||
const channelType = String(body?.channelType ?? '').trim();
|
||||
const accountId = String(body?.accountId ?? '').trim();
|
||||
|
||||
if (!channelType) {
|
||||
return fail(400, 'channelType is required');
|
||||
}
|
||||
if (!accountId) {
|
||||
return fail(400, 'accountId is required');
|
||||
}
|
||||
|
||||
setChannelDefaultAccount(channelType, accountId);
|
||||
ctx.gatewayManager.notifyRuntimeChanged({
|
||||
topics: ['channels', 'channel-targets', 'agents'],
|
||||
reason: 'channels:default-account-updated',
|
||||
channelType,
|
||||
accountId,
|
||||
});
|
||||
|
||||
return ok({
|
||||
success: true,
|
||||
channels: listSelectedChannelAccountGroups(listAgentsSnapshot(accounts, defaultAccountId)),
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/channels/config/enabled' && method === 'PUT') {
|
||||
try {
|
||||
const body = parseJsonBody<{
|
||||
channelType?: string;
|
||||
enabled?: boolean;
|
||||
}>(request.body);
|
||||
const channelType = String(body?.channelType ?? '').trim();
|
||||
|
||||
if (!channelType) {
|
||||
return fail(400, 'channelType is required');
|
||||
}
|
||||
|
||||
setChannelEnabled(channelType, body?.enabled !== false);
|
||||
ctx.gatewayManager.notifyRuntimeChanged({
|
||||
topics: ['channels', 'channel-targets', 'agents'],
|
||||
reason: 'channels:enabled-updated',
|
||||
channelType,
|
||||
});
|
||||
|
||||
return ok({
|
||||
success: true,
|
||||
channels: listSelectedChannelAccountGroups(listAgentsSnapshot(accounts, defaultAccountId)),
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/channels/binding' && method === 'PUT') {
|
||||
try {
|
||||
const body = parseJsonBody<{
|
||||
@@ -96,6 +175,56 @@ export async function handleChannelRoutes(
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/channels/config' && method === 'POST') {
|
||||
try {
|
||||
const body = parseJsonBody<{
|
||||
channelType?: string;
|
||||
accountId?: string | null;
|
||||
channelLabel?: string | null;
|
||||
accountName?: string | null;
|
||||
channelUrl?: string | null;
|
||||
enabled?: boolean;
|
||||
config?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
}>(request.body);
|
||||
const channelType = String(body?.channelType ?? '').trim();
|
||||
const accountId = String(body?.accountId ?? '').trim();
|
||||
|
||||
if (!channelType) {
|
||||
return fail(400, 'channelType is required');
|
||||
}
|
||||
|
||||
if (accountId && !isCanonicalChannelAccountId(accountId) && !hasStoredChannelAccount(channelType, accountId)) {
|
||||
return fail(400, 'Invalid accountId format. Use lowercase letters, numbers, hyphens, or underscores only (max 64 chars, must start with a letter or number).');
|
||||
}
|
||||
|
||||
saveChannelConfig({
|
||||
channelType,
|
||||
accountId: accountId || undefined,
|
||||
channelLabel: body?.channelLabel,
|
||||
accountName: body?.accountName,
|
||||
channelUrl: body?.channelUrl,
|
||||
enabled: body?.enabled,
|
||||
config: body?.config,
|
||||
metadata: body?.metadata,
|
||||
});
|
||||
|
||||
ctx.gatewayManager.notifyRuntimeChanged({
|
||||
topics: ['channels', 'channel-targets', 'agents'],
|
||||
reason: 'channels:config-saved',
|
||||
channelType,
|
||||
accountId: accountId || undefined,
|
||||
});
|
||||
|
||||
return ok({
|
||||
success: true,
|
||||
channels: listSelectedChannelAccountGroups(listAgentsSnapshot(accounts, defaultAccountId)),
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/channels/binding' && method === 'DELETE') {
|
||||
try {
|
||||
const body = parseJsonBody<{
|
||||
@@ -129,5 +258,46 @@ export async function handleChannelRoutes(
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/api/channels/config/') && method === 'GET') {
|
||||
try {
|
||||
const channelType = decodeURIComponent(pathname.slice('/api/channels/config/'.length));
|
||||
const accountId = request.url.searchParams.get('accountId')?.trim() || null;
|
||||
return ok({
|
||||
success: true,
|
||||
values: getChannelFormValues(channelType, accountId) ?? {},
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/api/channels/config/') && method === 'DELETE') {
|
||||
try {
|
||||
const channelType = decodeURIComponent(pathname.slice('/api/channels/config/'.length));
|
||||
const accountId = request.url.searchParams.get('accountId')?.trim() || null;
|
||||
|
||||
deleteChannelConfig(channelType, accountId);
|
||||
if (accountId) {
|
||||
clearChannelBinding(channelType, accountId, accounts, defaultAccountId);
|
||||
} else {
|
||||
clearAllChannelBindings(channelType, accounts, defaultAccountId);
|
||||
}
|
||||
|
||||
ctx.gatewayManager.notifyRuntimeChanged({
|
||||
topics: ['channels', 'channel-targets', 'agents'],
|
||||
reason: 'channels:config-deleted',
|
||||
channelType,
|
||||
accountId: accountId || undefined,
|
||||
});
|
||||
|
||||
return ok({
|
||||
success: true,
|
||||
channels: listSelectedChannelAccountGroups(listAgentsSnapshot(accounts, defaultAccountId)),
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user