247 lines
7.0 KiB
TypeScript
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,
|
|
);
|
|
});
|