070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { appendErrorStatusEvent } from '../../src/runtime/chat-events';
|
|
import type { ChatMessage } from '../../src/types';
|
|
|
|
const base: ChatMessage = { id: 'm1', role: 'assistant', content: '' };
|
|
|
|
describe('appendErrorStatusEvent', () => {
|
|
it('returns the message unchanged when detail is empty or whitespace', () => {
|
|
expect(appendErrorStatusEvent(base, '')).toBe(base);
|
|
expect(appendErrorStatusEvent(base, ' ')).toBe(base);
|
|
});
|
|
|
|
it('appends a status event with the given detail when there are no prior events', () => {
|
|
const next = appendErrorStatusEvent(base, 'boom');
|
|
expect(next).not.toBe(base);
|
|
expect(next.events).toEqual([{ kind: 'status', label: 'error', detail: 'boom' }]);
|
|
});
|
|
|
|
it('does not duplicate when the last event is an identical error status', () => {
|
|
const seeded: ChatMessage = {
|
|
...base,
|
|
events: [{ kind: 'status', label: 'error', detail: 'boom' }],
|
|
};
|
|
expect(appendErrorStatusEvent(seeded, 'boom')).toBe(seeded);
|
|
});
|
|
|
|
it('merges classification into the existing trailing error event with the same detail', () => {
|
|
// Reload/reattach race: the daemon-persisted error frame is read first
|
|
// (detail only), then the run finishes and onError arrives with the
|
|
// finalize-time classification. The helper must enrich in place, not no-op.
|
|
const seeded: ChatMessage = {
|
|
...base,
|
|
events: [{ kind: 'status', label: 'error', detail: 'quota gone' }],
|
|
};
|
|
const next = appendErrorStatusEvent(seeded, 'quota gone', 'RATE_LIMITED', {
|
|
failureCategory: 'rate_limit',
|
|
failureDetail: 'hard_quota',
|
|
});
|
|
expect(next).not.toBe(seeded);
|
|
expect(next.events).toHaveLength(1);
|
|
expect(next.events?.[0]).toEqual({
|
|
kind: 'status',
|
|
label: 'error',
|
|
detail: 'quota gone',
|
|
code: 'RATE_LIMITED',
|
|
failureCategory: 'rate_limit',
|
|
failureDetail: 'hard_quota',
|
|
});
|
|
});
|
|
|
|
it('no-ops when the new pass adds nothing beyond the existing error event', () => {
|
|
const seeded: ChatMessage = {
|
|
...base,
|
|
events: [
|
|
{
|
|
kind: 'status',
|
|
label: 'error',
|
|
detail: 'quota gone',
|
|
code: 'RATE_LIMITED',
|
|
failureCategory: 'rate_limit',
|
|
failureDetail: 'hard_quota',
|
|
},
|
|
],
|
|
};
|
|
expect(
|
|
appendErrorStatusEvent(seeded, 'quota gone', 'RATE_LIMITED', {
|
|
failureCategory: 'rate_limit',
|
|
failureDetail: 'hard_quota',
|
|
}),
|
|
).toBe(seeded);
|
|
});
|
|
|
|
it('appends when the previous error status detail differs', () => {
|
|
const seeded: ChatMessage = {
|
|
...base,
|
|
events: [{ kind: 'status', label: 'error', detail: 'first' }],
|
|
};
|
|
const next = appendErrorStatusEvent(seeded, 'second');
|
|
expect(next.events).toHaveLength(2);
|
|
expect(next.events?.[1]).toEqual({ kind: 'status', label: 'error', detail: 'second' });
|
|
});
|
|
|
|
it('preserves non-error events that precede the new one', () => {
|
|
const seeded: ChatMessage = {
|
|
...base,
|
|
events: [{ kind: 'text', text: 'hi' }, { kind: 'status', label: 'ok' }],
|
|
};
|
|
const next = appendErrorStatusEvent(seeded, 'fail');
|
|
expect(next.events).toEqual([
|
|
{ kind: 'text', text: 'hi' },
|
|
{ kind: 'status', label: 'ok' },
|
|
{ kind: 'status', label: 'error', detail: 'fail' },
|
|
]);
|
|
});
|
|
});
|