import { describe, expect, it, vi } from 'vitest'; import type { ToolSession } from '../../../../src/context/tools/tool-session.js'; import { createTouchedSlSources, hasTouchedSlSource } from '../../../../src/context/tools/touched-sl-sources.js'; import type { ToolContext } from '../../../../src/context/tools/base-tool.js'; import { SlRollbackTool } from '../../../../src/context/sl/tools/sl-rollback.tool.js'; function makeSession(overrides: Partial = {}): ToolSession { return { connectionId: 'conn-1', isWorktreeScoped: true, preHead: 'base', touchedSlSources: createTouchedSlSources([{ connectionId: 'conn-1', sourceName: 'orders' }]), actions: [{ target: 'sl', type: 'updated', key: 'orders', detail: 'x' }], semanticLayerService: {} as any, wikiService: {} as any, configService: { writeFile: vi.fn().mockResolvedValue(undefined), deleteFile: vi.fn().mockResolvedValue(undefined), } as any, gitService: { getFileAtCommit: vi.fn().mockResolvedValue('pre: content') } as any, ...overrides, }; } describe('SlRollbackTool', () => { const connections = { getConnectionById: vi.fn(), listEnabledConnections: vi.fn(), executeQuery: vi.fn(), }; it('errors when context.session is absent', async () => { const tool = new SlRollbackTool({} as never, connections as never, 1); const context: ToolContext = { sourceId: 's', messageId: 'm', userId: 'u' }; const result = await tool.call({ sourceName: 'orders' } as any, context); expect(result.structured.success).toBe(false); expect(result.markdown).toMatch(/session/i); }); it('errors when session has no connectionId (wiki-only turn)', async () => { const tool = new SlRollbackTool({} as never, connections as never, 1); const session = makeSession({ connectionId: null }); const context: ToolContext = { sourceId: 's', messageId: 'm', userId: 'u', session }; const result = await tool.call({ sourceName: 'orders' } as any, context); expect(result.structured.success).toBe(false); expect(result.markdown).toMatch(/connection-scoped session/i); // Session state untouched expect(hasTouchedSlSource(session.touchedSlSources, 'conn-1', 'orders')).toBe(true); expect((session.gitService as any).getFileAtCommit).not.toHaveBeenCalled(); }); it('restores the source content from preHead, clears touched set, prunes actions', async () => { const slSourcesRepository = { deleteByConnectionAndName: vi.fn().mockResolvedValue(undefined) }; const tool = new SlRollbackTool(slSourcesRepository as never, connections as never, 1); const session = makeSession(); const context: ToolContext = { sourceId: 's', messageId: 'm', userId: 'u', session }; const result = await tool.call({ sourceName: 'orders' } as any, context); expect(result.structured.success).toBe(true); expect((session.gitService as any).getFileAtCommit).toHaveBeenCalledWith( expect.stringContaining('orders.yaml'), 'base', ); expect((session.configService as any).writeFile).toHaveBeenCalled(); expect(hasTouchedSlSource(session.touchedSlSources, 'conn-1', 'orders')).toBe(false); expect(session.actions).toEqual([]); }); });