feat(voice): журнал обработанных вебхуков LiveKit

This commit is contained in:
2026-09-20 18:45:14 +03:00
parent ecd7b3be10
commit 0db4c63023
19 changed files with 1578 additions and 59 deletions
+42 -2
View File
@@ -13,6 +13,7 @@ import { useMessagesStore } from '@/stores/messages';
import { useSessionStore } from '@/stores/session';
import { useUiStore } from '@/stores/ui';
import { useVoiceStore } from '@/stores/voice';
import { useVoiceConnectionStore } from '@/stores/voiceConnection';
import type { User } from '@/api/types';
/** Ответ-заглушка с JSON-телом (как у настоящего API). */
@@ -32,7 +33,8 @@ export interface FetchRoute {
/** Подстрока URL, по которой выбирается обработчик. */
match: string;
method?: string;
response: () => Response | Promise<Response>;
/** Ответ на запрос: обработчик видит url, метод и разобранное тело. */
response: (request: RecordedRequest) => Response | Promise<Response>;
}
/**
@@ -54,7 +56,7 @@ export function installFetch(routes: FetchRoute[]) {
if (!url.includes(route.match)) {
continue;
}
return Promise.resolve(route.response());
return Promise.resolve(route.response({ url, method, body: parseRequestBody(init?.body) }));
}
return Promise.resolve(apiError('not_found', 404));
});
@@ -62,6 +64,18 @@ export function installFetch(routes: FetchRoute[]) {
return fetchMock;
}
/** Тело запроса как JSON: у multipart и пустого тела его нет. */
function parseRequestBody(body: BodyInit | null | undefined): unknown {
if (typeof body !== 'string') {
return undefined;
}
try {
return JSON.parse(body) as unknown;
} catch {
return undefined;
}
}
/** Мок fetch, который падает на уровне сети (сервер недоступен). */
export function installFailingFetch() {
const fetchMock = vi.fn(() => Promise.reject(new TypeError('Failed to fetch')));
@@ -136,6 +150,8 @@ export function resetStores(): void {
useSessionStore.getState().reset();
useMessagesStore.getState().reset();
useVoiceStore.getState().reset();
// Голосовая сессия могла остаться от прошлого теста: гасим её без запросов.
useVoiceConnectionStore.getState().reset();
useFriendsStore.getState().reset();
resetUiStore();
// `reset` закрывает и общее соединение шлюза: иначе колбэки прошлого теста
@@ -205,6 +221,18 @@ interface SnapshotGuildInput {
my_nickname?: string;
/** Кастомные эмодзи сервера (AGENT.md 7.12). */
emojis?: { id: string; name: string; file_id: string; animated?: boolean; token?: string }[];
/** Кто сидит в голосовых комнатах (AGENT.md 7.14). */
voice_states?: {
user_id: string;
channel_id: string;
self_mute?: boolean;
self_deaf?: boolean;
server_mute?: boolean;
server_deaf?: boolean;
camera?: boolean;
screen?: boolean;
joined_at?: string;
}[];
channels?: {
id: string;
name: string;
@@ -269,6 +297,18 @@ export function makeReadySnapshot(
animated: emoji.animated ?? false,
token: emoji.token ?? `<:${emoji.name}:${emoji.file_id}>`,
})),
voice_states: (guild.voice_states ?? []).map((state, index) => ({
user_id: state.user_id,
channel_id: state.channel_id,
guild_id: guild.id,
self_mute: state.self_mute ?? false,
self_deaf: state.self_deaf ?? false,
server_mute: state.server_mute ?? false,
server_deaf: state.server_deaf ?? false,
camera: state.camera ?? false,
screen: state.screen ?? false,
joined_at: state.joined_at ?? `2026-09-19T10:0${String(index)}:00Z`,
})),
})),
dm_channels: dmChannels.map((channel) => ({
id: channel.id,