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:
184
electron/utils/channel-status.ts
Normal file
184
electron/utils/channel-status.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user