feat: v2.6 follow-ups — apps/server close-hook caller + DiffPanel staging hint (3.7)
apps/server fire-and-forgets BooCoder's Phase-3 close hooks (new coder-notify.ts, reuses BOOCODER_URL, never-rejects) on session-delete + chat archive/archive-all/delete, so warm backends + worktrees tear down immediately (idle-evict/reaper was the backstop). 3.7: BooCoder DiffPanel shows a muted one-liner when the selected provider can't see another agent's unapplied worktree edits (pure derivation from per-change agent + current provider, no new state). 6 new server tests (coder-notify); 537 server tests pass; web+server tsc/build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
67
apps/server/src/services/__tests__/coder-notify.test.ts
Normal file
67
apps/server/src/services/__tests__/coder-notify.test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
// v2.6.10 Phase 3 (server wiring) — notifyCoderClose fire-and-forget helper.
|
||||
//
|
||||
// The guarantee under test: the helper NEVER throws (so it can't break the
|
||||
// user's delete/archive path), targets the correct coder URL shape, and folds
|
||||
// every failure mode (non-2xx, network error) into a `false` result.
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { notifyCoderClose } from '../coder-notify.js';
|
||||
|
||||
const ORIGINAL_BOOCODER_URL = process.env.BOOCODER_URL;
|
||||
|
||||
describe('notifyCoderClose', () => {
|
||||
beforeEach(() => {
|
||||
delete process.env.BOOCODER_URL;
|
||||
});
|
||||
afterEach(() => {
|
||||
if (ORIGINAL_BOOCODER_URL === undefined) delete process.env.BOOCODER_URL;
|
||||
else process.env.BOOCODER_URL = ORIGINAL_BOOCODER_URL;
|
||||
});
|
||||
|
||||
it('POSTs the chat close hook at the default coder origin and resolves true on 2xx', async () => {
|
||||
const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
|
||||
const ok = await notifyCoderClose('chat', 'chat-123', undefined, fetcher as unknown as typeof fetch);
|
||||
expect(ok).toBe(true);
|
||||
expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
const [url, init] = fetcher.mock.calls[0]!;
|
||||
expect(url).toBe('http://boocoder:3000/api/chats/chat-123/close');
|
||||
expect(init).toEqual({ method: 'POST' });
|
||||
});
|
||||
|
||||
it('POSTs the session close hook with the sessions segment', async () => {
|
||||
const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
|
||||
const ok = await notifyCoderClose('session', 'sess-abc', undefined, fetcher as unknown as typeof fetch);
|
||||
expect(ok).toBe(true);
|
||||
expect(fetcher.mock.calls[0]![0]).toBe('http://boocoder:3000/api/sessions/sess-abc/close');
|
||||
});
|
||||
|
||||
it('honors BOOCODER_URL for the origin', async () => {
|
||||
process.env.BOOCODER_URL = 'http://100.114.205.53:9502';
|
||||
const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
|
||||
await notifyCoderClose('chat', 'c1', undefined, fetcher as unknown as typeof fetch);
|
||||
expect(fetcher.mock.calls[0]![0]).toBe('http://100.114.205.53:9502/api/chats/c1/close');
|
||||
});
|
||||
|
||||
it('resolves false on a non-2xx response (does not throw)', async () => {
|
||||
const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 500 }));
|
||||
const log = { debug: vi.fn() };
|
||||
const ok = await notifyCoderClose('chat', 'c1', log, fetcher as unknown as typeof fetch);
|
||||
expect(ok).toBe(false);
|
||||
expect(log.debug).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('resolves false on a network error (coder unreachable) — never rejects', async () => {
|
||||
const fetcher = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
|
||||
const log = { debug: vi.fn() };
|
||||
const ok = await notifyCoderClose('session', 's1', log, fetcher as unknown as typeof fetch);
|
||||
expect(ok).toBe(false);
|
||||
expect(log.debug).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not require a logger', async () => {
|
||||
const fetcher = vi.fn().mockRejectedValue(new Error('boom'));
|
||||
await expect(
|
||||
notifyCoderClose('chat', 'c1', undefined, fetcher as unknown as typeof fetch),
|
||||
).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user