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:
duanshuwen
2026-04-18 16:12:49 +08:00
parent ee72cf7261
commit ef46c73c3e
26 changed files with 4056 additions and 186 deletions

View File

@@ -0,0 +1,184 @@
import type { ChannelAccountCatalogGroup, ChannelConnectionStatus } from '@src/lib/channel-types';
const KNOWN_CHANNEL_STATUSES: ChannelConnectionStatus[] = [
'connected',
'connecting',
'disconnected',
'error',
'degraded',
];
export interface ChannelStatusInferenceInput {
status?: unknown;
configured?: boolean;
channelUrl?: string | null;
lastError?: string | null;
error?: unknown;
warnings?: readonly string[] | null;
warningCount?: number;
isConnecting?: boolean;
isLoading?: boolean;
connectionState?: unknown;
hasBinding?: boolean;
degraded?: boolean;
}
export interface ChannelStatusSummary {
status: ChannelConnectionStatus;
counts: Record<ChannelConnectionStatus, number>;
groupCount: number;
accountCount: number;
}
function isMeaningfulText(value: unknown): boolean {
return String(value ?? '').trim().length > 0;
}
function isValidUrl(value: string): boolean {
try {
new URL(value);
return true;
} catch {
return false;
}
}
export function normalizeChannelConnectionStatus(value: unknown): ChannelConnectionStatus | null {
const normalized = String(value ?? '').trim().toLowerCase();
return KNOWN_CHANNEL_STATUSES.includes(normalized as ChannelConnectionStatus)
? (normalized as ChannelConnectionStatus)
: null;
}
export function inferChannelConnectionStatus(
input: ChannelStatusInferenceInput = {},
): ChannelConnectionStatus {
const explicitStatus = normalizeChannelConnectionStatus(input.status);
const normalizedConnectionState = normalizeChannelConnectionStatus(input.connectionState);
const channelUrl = String(input.channelUrl ?? '').trim();
const warningCount = typeof input.warningCount === 'number'
? input.warningCount
: Array.isArray(input.warnings)
? input.warnings.filter(isMeaningfulText).length
: 0;
if (isMeaningfulText(input.error) || isMeaningfulText(input.lastError)) {
return 'error';
}
if (explicitStatus === 'error' || normalizedConnectionState === 'error') {
return 'error';
}
if (input.configured === false || !channelUrl) {
return 'disconnected';
}
if (!isValidUrl(channelUrl)) {
return 'error';
}
if (
explicitStatus === 'connecting'
|| normalizedConnectionState === 'connecting'
|| input.isConnecting
|| input.isLoading
) {
return 'connecting';
}
if (explicitStatus === 'disconnected' || normalizedConnectionState === 'disconnected') {
return 'disconnected';
}
if (explicitStatus === 'degraded' || normalizedConnectionState === 'degraded') {
return 'degraded';
}
if (warningCount > 0 || input.degraded || input.hasBinding === false) {
return 'degraded';
}
if (explicitStatus === 'connected') {
return 'connected';
}
return 'connected';
}
export function summarizeChannelConnectionStatuses(
statuses: readonly ChannelConnectionStatus[],
): ChannelConnectionStatus {
const counts = statuses.reduce<Record<ChannelConnectionStatus, number>>(
(acc, status) => {
acc[status] += 1;
return acc;
},
{
connected: 0,
connecting: 0,
disconnected: 0,
error: 0,
degraded: 0,
},
);
if (counts.error > 0) {
return counts.connected > 0 || counts.connecting > 0 || counts.degraded > 0 || counts.disconnected > 0
? 'degraded'
: 'error';
}
if (counts.connecting > 0) {
return 'connecting';
}
if (counts.degraded > 0) {
return 'degraded';
}
if (counts.disconnected > 0 && counts.connected === 0) {
return 'disconnected';
}
if (counts.connected > 0 && counts.disconnected > 0) {
return 'degraded';
}
return counts.connected > 0 ? 'connected' : 'disconnected';
}
export function buildChannelStatusSummary(
groups: readonly ChannelAccountCatalogGroup[],
): ChannelStatusSummary {
const counts: Record<ChannelConnectionStatus, number> = {
connected: 0,
connecting: 0,
disconnected: 0,
error: 0,
degraded: 0,
};
let accountCount = 0;
for (const group of groups) {
accountCount += group.accounts.length;
for (const account of group.accounts) {
counts[account.status] += 1;
}
}
const accountStatuses: ChannelConnectionStatus[] = [];
for (const group of groups) {
for (const account of group.accounts) {
accountStatuses.push(account.status);
}
}
return {
status: summarizeChannelConnectionStatuses(accountStatuses),
counts,
groupCount: groups.length,
accountCount,
};
}