232 lines
6.5 KiB
TypeScript
232 lines
6.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { NewMessage, PairedTask, PairedTurnOutput } from './types.js';
|
|
import { buildWebDashboardRoomActivity } from './web-dashboard-data.js';
|
|
|
|
function makePairedTask(overrides: Partial<PairedTask>): PairedTask {
|
|
return {
|
|
id: 'paired-1',
|
|
chat_jid: 'dc:ops',
|
|
group_folder: 'ops',
|
|
owner_service_id: 'codex-main',
|
|
reviewer_service_id: 'claude-reviewer',
|
|
owner_agent_type: 'codex',
|
|
reviewer_agent_type: 'claude-code',
|
|
arbiter_agent_type: 'codex',
|
|
title: 'Dashboard PR',
|
|
source_ref: null,
|
|
plan_notes: null,
|
|
review_requested_at: null,
|
|
round_trip_count: 0,
|
|
owner_failure_count: 0,
|
|
owner_step_done_streak: 0,
|
|
finalize_step_done_count: 0,
|
|
task_done_then_user_reopen_count: 0,
|
|
empty_step_done_streak: 0,
|
|
status: 'review_ready',
|
|
arbiter_verdict: null,
|
|
arbiter_requested_at: null,
|
|
completion_reason: null,
|
|
created_at: '2026-04-26T04:00:00.000Z',
|
|
updated_at: '2026-04-26T04:30:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
const roomEntry = {
|
|
jid: 'dc:ops',
|
|
name: '#ops',
|
|
folder: 'ops',
|
|
agentType: 'codex' as const,
|
|
status: 'inactive' as const,
|
|
elapsedMs: null,
|
|
pendingMessages: false,
|
|
pendingTasks: 0,
|
|
};
|
|
|
|
describe('web dashboard attachment data', () => {
|
|
it('normalizes structured EJClaw envelopes in room messages and outputs', () => {
|
|
const task = makePairedTask({ id: 'paired-structured' });
|
|
const structured = JSON.stringify({
|
|
ejclaw: {
|
|
visibility: 'public',
|
|
text: '라벨 좌측 클리핑 회귀 수정했습니다.',
|
|
verdict: 'done',
|
|
attachments: [
|
|
{
|
|
path: '/tmp/bar-chart-label-fit-playwright.png',
|
|
name: 'bar-chart-label-fit-playwright.png',
|
|
mime: 'image/png',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
const output: PairedTurnOutput = {
|
|
id: 1,
|
|
task_id: task.id,
|
|
turn_number: 1,
|
|
role: 'owner',
|
|
output_text: structured,
|
|
verdict: 'done',
|
|
created_at: '2026-04-26T05:30:00.000Z',
|
|
};
|
|
const message: NewMessage = {
|
|
id: 'msg-structured',
|
|
chat_jid: 'dc:ops',
|
|
sender: 'bot-1',
|
|
sender_name: 'owner',
|
|
content: structured,
|
|
timestamp: '2026-04-26T05:31:00.000Z',
|
|
is_from_me: true,
|
|
is_bot_message: true,
|
|
message_source_kind: 'bot',
|
|
};
|
|
|
|
const activity = buildWebDashboardRoomActivity({
|
|
serviceId: 'codex-main',
|
|
entry: roomEntry,
|
|
pairedTask: task,
|
|
turns: [],
|
|
attempts: [],
|
|
outputs: [output],
|
|
messages: [message],
|
|
});
|
|
|
|
expect(activity.messages[0]).toMatchObject({
|
|
content: '라벨 좌측 클리핑 회귀 수정했습니다.',
|
|
attachments: [
|
|
{
|
|
path: '/tmp/bar-chart-label-fit-playwright.png',
|
|
name: 'bar-chart-label-fit-playwright.png',
|
|
mime: 'image/png',
|
|
},
|
|
],
|
|
});
|
|
expect(activity.messages[0]?.content).not.toContain('"ejclaw"');
|
|
expect(activity.pairedTask?.outputs[0]).toMatchObject({
|
|
outputText: '라벨 좌측 클리핑 회귀 수정했습니다.',
|
|
attachments: [
|
|
{
|
|
path: '/tmp/bar-chart-label-fit-playwright.png',
|
|
name: 'bar-chart-label-fit-playwright.png',
|
|
mime: 'image/png',
|
|
},
|
|
],
|
|
});
|
|
expect(activity.pairedTask?.outputs[0]?.outputText).not.toContain(
|
|
'"ejclaw"',
|
|
);
|
|
});
|
|
|
|
it('turns legacy Discord image placeholders into dashboard attachments', () => {
|
|
const message: NewMessage = {
|
|
id: 'msg-image',
|
|
chat_jid: 'dc:ops',
|
|
sender: 'bot-1',
|
|
sender_name: 'owner',
|
|
content:
|
|
'라벨 좌측 클리핑 회귀 수정했습니다.\n[Image: screenshot.png → /tmp/bar-chart-label-fit-playwright.png]',
|
|
timestamp: '2026-04-26T05:31:00.000Z',
|
|
is_from_me: true,
|
|
is_bot_message: true,
|
|
message_source_kind: 'bot',
|
|
};
|
|
|
|
const activity = buildWebDashboardRoomActivity({
|
|
serviceId: 'codex-main',
|
|
entry: roomEntry,
|
|
pairedTask: null,
|
|
turns: [],
|
|
attempts: [],
|
|
outputs: [],
|
|
messages: [message],
|
|
});
|
|
|
|
expect(activity.messages[0]).toMatchObject({
|
|
content: '라벨 좌측 클리핑 회귀 수정했습니다.',
|
|
attachments: [
|
|
{
|
|
path: '/tmp/bar-chart-label-fit-playwright.png',
|
|
name: 'bar-chart-label-fit-playwright.png',
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('shows stored paired turn output attachments in room activity', () => {
|
|
const task = makePairedTask({ id: 'paired-output-attachments' });
|
|
const output: PairedTurnOutput = {
|
|
id: 1,
|
|
task_id: task.id,
|
|
turn_number: 1,
|
|
role: 'owner',
|
|
output_text: 'TASK_DONE\n\n새 렌더 증거 첨부',
|
|
attachments: [
|
|
{
|
|
path: '/tmp/settings-v0.1.92-deployed-390.png',
|
|
name: 'settings-v0.1.92-deployed-390.png',
|
|
mime: 'image/png',
|
|
},
|
|
],
|
|
verdict: 'task_done',
|
|
created_at: '2026-04-26T05:30:00.000Z',
|
|
};
|
|
|
|
const activity = buildWebDashboardRoomActivity({
|
|
serviceId: 'codex-main',
|
|
entry: roomEntry,
|
|
pairedTask: task,
|
|
turns: [],
|
|
attempts: [],
|
|
outputs: [output],
|
|
messages: [],
|
|
});
|
|
|
|
expect(activity.pairedTask?.outputs[0]).toMatchObject({
|
|
outputText: 'TASK_DONE\n\n새 렌더 증거 첨부',
|
|
attachments: [
|
|
{
|
|
path: '/tmp/settings-v0.1.92-deployed-390.png',
|
|
name: 'settings-v0.1.92-deployed-390.png',
|
|
mime: 'image/png',
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('turns markdown image output into dashboard attachments', () => {
|
|
const message: NewMessage = {
|
|
id: 'msg-markdown-image',
|
|
chat_jid: 'dc:ops',
|
|
sender: 'bot-1',
|
|
sender_name: 'owner',
|
|
content:
|
|
'라벨 좌측 클리핑 회귀 수정했습니다.\n',
|
|
timestamp: '2026-04-26T05:31:00.000Z',
|
|
is_from_me: true,
|
|
is_bot_message: true,
|
|
message_source_kind: 'bot',
|
|
};
|
|
|
|
const activity = buildWebDashboardRoomActivity({
|
|
serviceId: 'codex-main',
|
|
entry: roomEntry,
|
|
pairedTask: null,
|
|
turns: [],
|
|
attempts: [],
|
|
outputs: [],
|
|
messages: [message],
|
|
});
|
|
|
|
expect(activity.messages[0]).toMatchObject({
|
|
content: '라벨 좌측 클리핑 회귀 수정했습니다.',
|
|
attachments: [
|
|
{
|
|
path: '/tmp/bar-chart-label-fit-playwright.png',
|
|
name: 'bar-chart-label-fit-playwright.png',
|
|
},
|
|
],
|
|
});
|
|
});
|
|
});
|