feat(chat): личный чат тьютор ↔ админ с вложениями
Диалоги 1:1 (один тред на тьютора), отдельный SSE-канал whatido:chat вместо общей ленты notifications, вложения с белым списком расширений и вставкой из буфера (Ctrl+V), очистка по chat_retention_days. Таблицы в бэкап, chat_attachments добавлен в sweepOrphanedUploads.
This commit is contained in:
@@ -305,6 +305,46 @@ async function main() {
|
||||
(lessonNotifyMeta.data.types || []).some(t => t.key === 'notify_lesson_ai_formatted'),
|
||||
(lessonNotifyMeta.data.types || []).map(t => t.key));
|
||||
|
||||
// --- Chat (тьютор ↔ админ) ---
|
||||
const chatNoAuth = await api('/api/chat/threads');
|
||||
ok('chat: список без токена -> 401', chatNoAuth.status === 401, chatNoAuth.status);
|
||||
const chatThreads = await api('/api/chat/threads', { token });
|
||||
ok('chat: список диалогов -> 200', chatThreads.status === 200 && Array.isArray(chatThreads.data.items) && typeof chatThreads.data.unread === 'number', chatThreads.status);
|
||||
const chatUnreadGet = await api('/api/chat/unread', { token });
|
||||
ok('chat: счётчик непрочитанных', chatUnreadGet.status === 200 && typeof chatUnreadGet.data.unread === 'number', chatUnreadGet.data);
|
||||
const chatStream = await fetch(BASE + '/api/chat/stream?token=' + encodeURIComponent(token));
|
||||
ok('chat: SSE открывается', chatStream.status === 200, chatStream.status);
|
||||
if (chatStream.status === 200) {
|
||||
const chatReader = chatStream.body.getReader();
|
||||
const chatFirst = await chatReader.read();
|
||||
const chatText = new TextDecoder().decode(chatFirst.value || new Uint8Array());
|
||||
ok('chat: SSE отдаёт ready-кадр', chatText.includes('event: ready') || chatText.includes(':ok'), JSON.stringify(chatText.slice(0, 60)));
|
||||
chatReader.cancel().catch(() => {});
|
||||
}
|
||||
const chatBadId = await api('/api/chat/threads/not-a-number/messages', { token });
|
||||
ok('chat: некорректный id диалога -> 400', chatBadId.status === 400, chatBadId.status);
|
||||
const chatMissing = await api('/api/chat/threads/99999999/messages', { token });
|
||||
ok('chat: несуществующий диалог -> 404', chatMissing.status === 404, chatMissing.status);
|
||||
const chatAdminThread = await api('/api/chat/threads', { token, method: 'POST', body: { user_id: me1.data.id } });
|
||||
ok('chat: админ создаёт диалог с пользователем', chatAdminThread.status === 200 && chatAdminThread.data.thread && chatAdminThread.data.thread.id > 0, chatAdminThread.data);
|
||||
if (chatAdminThread.status === 200 && chatAdminThread.data.thread) {
|
||||
const tid = chatAdminThread.data.thread.id;
|
||||
const chatMsg = await api(`/api/chat/threads/${tid}/messages`, { token, method: 'POST', body: { body: 'Smoke: чат работает' } });
|
||||
ok('chat: отправка сообщения', chatMsg.status === 200 && chatMsg.data.message && chatMsg.data.message.id > 0, chatMsg.data);
|
||||
const chatMsgList = await api(`/api/chat/threads/${tid}/messages`, { token });
|
||||
ok('chat: чтение сообщений', chatMsgList.status === 200 && Array.isArray(chatMsgList.data.items) && chatMsgList.data.items.some(m => m.body === 'Smoke: чат работает'), (chatMsgList.data.items || []).map(m => m.body));
|
||||
const chatEmpty = await api(`/api/chat/threads/${tid}/messages`, { token, method: 'POST', body: { body: ' ' } });
|
||||
ok('chat: пустое сообщение -> 400', chatEmpty.status === 400, chatEmpty.status);
|
||||
const chatRead = await api(`/api/chat/threads/${tid}/read`, { token, method: 'POST' });
|
||||
ok('chat: отметка прочитанным', chatRead.status === 200 && typeof chatRead.data.unread === 'number', chatRead.data);
|
||||
const chatFileNoAuth = await fetch(BASE + '/api/chat/files/deadbeef');
|
||||
ok('chat: файл без токена -> 401', chatFileNoAuth.status === 401, chatFileNoAuth.status);
|
||||
const chatFileMissing = await api('/api/chat/files/deadbeef', { token });
|
||||
ok('chat: несуществующий файл -> 404', chatFileMissing.status === 404, chatFileMissing.status);
|
||||
}
|
||||
const chatBadUser = await api('/api/chat/threads', { token, method: 'POST', body: { user_id: 99999999 } });
|
||||
ok('chat: несуществующий тьютор -> 404', chatBadUser.status === 404, chatBadUser.status);
|
||||
|
||||
const logout = await api('/api/auth/logout', { token, method: 'POST' });
|
||||
ok('logout', logout.status === 200, logout.status);
|
||||
const afterLogout = await api('/api/auth/me', { token });
|
||||
|
||||
Reference in New Issue
Block a user