- 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.
185 lines
4.4 KiB
TypeScript
185 lines
4.4 KiB
TypeScript
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,
|
|
};
|
|
}
|