Files
ai-code-app/etc/servers/work-server/src/services/chat-room-service.test.ts

247 lines
7.0 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
buildChatConversationRequestPatchFromMessage,
isVisibleConversationMessage,
mergeChatConversationRequestStatus,
resolveNextConversationChatTypeId,
shouldClearConversationJobState,
selectChatConversationResponseCandidate,
} from './chat-room-service.js';
test('mergeChatConversationRequestStatus keeps terminal states from being downgraded', () => {
assert.equal(mergeChatConversationRequestStatus('completed', 'accepted'), 'completed');
assert.equal(mergeChatConversationRequestStatus('started', 'accepted'), 'started');
assert.equal(mergeChatConversationRequestStatus('queued', 'accepted'), 'queued');
assert.equal(mergeChatConversationRequestStatus('accepted', 'completed'), 'completed');
});
test('resolveNextConversationChatTypeId prefers the requested chat type over the stored one', () => {
assert.equal(resolveNextConversationChatTypeId('general-request', 'api-request-template'), 'api-request-template');
assert.equal(resolveNextConversationChatTypeId('general-request', ' api-request-template '), 'api-request-template');
});
test('resolveNextConversationChatTypeId falls back to the stored chat type when the request is empty', () => {
assert.equal(resolveNextConversationChatTypeId('general-request', null), 'general-request');
assert.equal(resolveNextConversationChatTypeId('general-request', ' '), 'general-request');
assert.equal(resolveNextConversationChatTypeId(null, null), null);
});
test('buildChatConversationRequestPatchFromMessage ignores system progress messages', () => {
assert.equal(
buildChatConversationRequestPatchFromMessage({
id: 10,
author: 'system',
text: '요청 실행 중입니다.',
clientRequestId: 'chat-req-1',
}),
null,
);
});
test('isVisibleConversationMessage hides internal system messages and keeps activity logs', () => {
assert.equal(
isVisibleConversationMessage({
id: 1,
author: 'system',
text: '응답을 준비하고 있습니다.',
timestamp: '2026-04-22 10:00:00',
}),
false,
);
assert.equal(
isVisibleConversationMessage({
id: 2,
author: 'system',
text: '[[activity-log]]\n작업을 시작했습니다.',
timestamp: '2026-04-22 10:00:01',
}),
true,
);
});
test('buildChatConversationRequestPatchFromMessage builds user and codex request patches', () => {
assert.deepEqual(
buildChatConversationRequestPatchFromMessage({
id: 11,
author: 'user',
text: '질문',
clientRequestId: 'chat-req-2',
}),
{
requestId: 'chat-req-2',
status: 'accepted',
userMessageId: 11,
userText: '질문',
},
);
assert.deepEqual(
buildChatConversationRequestPatchFromMessage({
id: 12,
author: 'codex',
text: '답변',
clientRequestId: 'chat-req-2',
}),
{
requestId: 'chat-req-2',
status: 'started',
responseMessageId: 12,
responseText: '답변',
},
);
});
test('selectChatConversationResponseCandidate falls back to codex replies in the request window', () => {
const candidate = selectChatConversationResponseCandidate(
{
requestId: 'chat-req-3',
createdAt: '2026-04-18T14:00:00.000Z',
responseMessageId: null,
},
{
createdAt: '2026-04-18T14:10:00.000Z',
},
[
{
id: 1,
messageId: 1001,
author: 'codex',
text: '이전 답변',
clientRequestId: null,
createdAt: '2026-04-18T13:59:00.000Z',
},
{
id: 2,
messageId: 1002,
author: 'codex',
text: '현재 요청 답변',
clientRequestId: null,
createdAt: '2026-04-18T14:05:00.000Z',
},
{
id: 3,
messageId: 1003,
author: 'codex',
text: '다음 요청 답변',
clientRequestId: null,
createdAt: '2026-04-18T14:11:00.000Z',
},
],
);
assert.deepEqual(candidate, {
id: 2,
messageId: 1002,
author: 'codex',
text: '현재 요청 답변',
clientRequestId: null,
createdAt: '2026-04-18T14:05:00.000Z',
});
});
test('shouldClearConversationJobState clears stale job state when terminal request already has a response', () => {
assert.equal(
shouldClearConversationJobState({
currentRequestId: 'chat-req-4',
currentJobStatus: 'started',
request: {
requestId: 'chat-req-4',
status: 'completed',
responseMessageId: 101,
responseText: '답변',
terminalAt: '2026-04-19T01:00:00.000Z',
},
}),
true,
);
});
test('shouldClearConversationJobState clears orphaned job state without a current request id', () => {
assert.equal(
shouldClearConversationJobState({
currentRequestId: null,
currentJobStatus: 'started',
request: null,
}),
true,
);
});
test('shouldClearConversationJobState keeps active job state when request is still running without a response', () => {
assert.equal(
shouldClearConversationJobState({
currentRequestId: 'chat-req-5',
currentJobStatus: 'started',
request: {
requestId: 'chat-req-5',
status: 'started',
responseMessageId: null,
responseText: '',
terminalAt: null,
},
}),
false,
);
});
test('shouldClearConversationJobState does not clear placeholder-only started responses', () => {
assert.equal(
shouldClearConversationJobState({
currentRequestId: 'chat-req-6',
currentJobStatus: 'started',
request: {
requestId: 'chat-req-6',
status: 'started',
responseMessageId: 301,
responseText: '응답을 준비하고 있습니다...',
terminalAt: null,
},
}),
false,
);
});
test('shouldClearConversationJobState clears stale placeholder-only started responses when runtime is gone', () => {
assert.equal(
shouldClearConversationJobState({
currentRequestId: 'chat-req-7',
currentJobStatus: 'started',
currentStatusUpdatedAt: '2026-04-19T08:08:35.813Z',
runtimeActive: false,
nowMs: Date.parse('2026-04-19T08:11:36.000Z'),
request: {
requestId: 'chat-req-7',
status: 'started',
responseMessageId: 302,
responseText: '응답을 준비하고 있습니다...',
terminalAt: null,
updatedAt: '2026-04-19T08:08:56.086Z',
},
}),
true,
);
});
test('shouldClearConversationJobState keeps placeholder-only started responses while runtime is active', () => {
assert.equal(
shouldClearConversationJobState({
currentRequestId: 'chat-req-8',
currentJobStatus: 'started',
currentStatusUpdatedAt: '2026-04-19T08:08:35.813Z',
runtimeActive: true,
nowMs: Date.parse('2026-04-19T08:11:36.000Z'),
request: {
requestId: 'chat-req-8',
status: 'started',
responseMessageId: 303,
responseText: '응답을 준비하고 있습니다...',
terminalAt: null,
updatedAt: '2026-04-19T08:08:56.086Z',
},
}),
false,
);
});