mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-11 14:40:52 -07:00
741 lines
22 KiB
TypeScript
741 lines
22 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import type { Config } from '@google/gemini-cli-core';
|
|
import { ChatRecordingService } from '@google/gemini-cli-core';
|
|
import { listSessions, deleteSession } from './sessions.js';
|
|
import { SessionSelector, type SessionInfo } from './sessionUtils.js';
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
writeToStdout: vi.fn(),
|
|
writeToStderr: vi.fn(),
|
|
}));
|
|
|
|
// Mock the SessionSelector and ChatRecordingService
|
|
vi.mock('./sessionUtils.js', () => ({
|
|
SessionSelector: vi.fn(),
|
|
formatRelativeTime: vi.fn(() => 'some time ago'),
|
|
}));
|
|
|
|
vi.mock('@google/gemini-cli-core', async () => {
|
|
const actual = await vi.importActual('@google/gemini-cli-core');
|
|
return {
|
|
...actual,
|
|
ChatRecordingService: vi.fn(),
|
|
generateSummary: vi.fn().mockResolvedValue(undefined),
|
|
writeToStdout: mocks.writeToStdout,
|
|
writeToStderr: mocks.writeToStderr,
|
|
};
|
|
});
|
|
|
|
describe('listSessions', () => {
|
|
let mockConfig: Config;
|
|
let mockListSessions: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
// Create mock config
|
|
mockConfig = {
|
|
storage: {
|
|
getProjectTempDir: vi.fn().mockReturnValue('/tmp/test-project'),
|
|
},
|
|
getSessionId: vi.fn().mockReturnValue('current-session-id'),
|
|
} as unknown as Config;
|
|
|
|
// Create mock listSessions method
|
|
mockListSessions = vi.fn();
|
|
|
|
// Mock SessionSelector constructor to return object with listSessions method
|
|
vi.mocked(SessionSelector).mockImplementation(
|
|
() =>
|
|
({
|
|
listSessions: mockListSessions,
|
|
}) as unknown as InstanceType<typeof SessionSelector>,
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.writeToStdout.mockClear();
|
|
mocks.writeToStderr.mockClear();
|
|
});
|
|
|
|
it('should display message when no previous sessions were found', async () => {
|
|
// Arrange: Return empty array from listSessions
|
|
mockListSessions.mockResolvedValue([]);
|
|
|
|
// Act
|
|
await listSessions(mockConfig);
|
|
|
|
// Assert
|
|
expect(mockListSessions).toHaveBeenCalledOnce();
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
'No previous sessions found for this project.',
|
|
);
|
|
});
|
|
|
|
it('should list sessions when sessions are found', async () => {
|
|
// Arrange: Create test sessions
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
|
const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000);
|
|
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-2025-01-18T12-00-00-session-1',
|
|
fileName: 'session-2025-01-18T12-00-00-session-1.json',
|
|
startTime: twoDaysAgo.toISOString(),
|
|
lastUpdated: twoDaysAgo.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'First user message',
|
|
firstUserMessage: 'First user message',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
{
|
|
id: 'session-2',
|
|
file: 'session-2025-01-20T11-00-00-session-2',
|
|
fileName: 'session-2025-01-20T11-00-00-session-2.json',
|
|
startTime: oneHourAgo.toISOString(),
|
|
lastUpdated: oneHourAgo.toISOString(),
|
|
messageCount: 10,
|
|
displayName: 'Second user message',
|
|
firstUserMessage: 'Second user message',
|
|
isCurrentSession: false,
|
|
index: 2,
|
|
},
|
|
{
|
|
id: 'current-session-id',
|
|
file: 'session-2025-01-20T12-00-00-current-s',
|
|
fileName: 'session-2025-01-20T12-00-00-current-s.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 3,
|
|
displayName: 'Current session',
|
|
firstUserMessage: 'Current session',
|
|
isCurrentSession: true,
|
|
index: 3,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await listSessions(mockConfig);
|
|
|
|
// Assert
|
|
expect(mockListSessions).toHaveBeenCalledOnce();
|
|
|
|
// Check that the header was displayed
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
'\nAvailable sessions for this project (3):\n',
|
|
);
|
|
|
|
// Check that each session was logged
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('1. First user message'),
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('[session-1]'),
|
|
);
|
|
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('2. Second user message'),
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('[session-2]'),
|
|
);
|
|
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('3. Current session'),
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining(', current)'),
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('[current-session-id]'),
|
|
);
|
|
});
|
|
|
|
it('should sort sessions by start time (oldest first)', async () => {
|
|
// Arrange: Create sessions in non-chronological order
|
|
const session1Time = new Date('2025-01-18T12:00:00.000Z');
|
|
const session2Time = new Date('2025-01-19T12:00:00.000Z');
|
|
const session3Time = new Date('2025-01-20T12:00:00.000Z');
|
|
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-2',
|
|
file: 'session-2',
|
|
fileName: 'session-2.json',
|
|
startTime: session2Time.toISOString(), // Middle
|
|
lastUpdated: session2Time.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Middle session',
|
|
firstUserMessage: 'Middle session',
|
|
isCurrentSession: false,
|
|
index: 2,
|
|
},
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-1',
|
|
fileName: 'session-1.json',
|
|
startTime: session1Time.toISOString(), // Oldest
|
|
lastUpdated: session1Time.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Oldest session',
|
|
firstUserMessage: 'Oldest session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
{
|
|
id: 'session-3',
|
|
file: 'session-3',
|
|
fileName: 'session-3.json',
|
|
startTime: session3Time.toISOString(), // Newest
|
|
lastUpdated: session3Time.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Newest session',
|
|
firstUserMessage: 'Newest session',
|
|
isCurrentSession: false,
|
|
index: 3,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await listSessions(mockConfig);
|
|
|
|
// Assert
|
|
// Get all the session log calls (skip the header)
|
|
const sessionCalls = mocks.writeToStdout.mock.calls.filter(
|
|
(call): call is [string] =>
|
|
typeof call[0] === 'string' &&
|
|
call[0].includes('[session-') &&
|
|
!call[0].includes('Available sessions'),
|
|
);
|
|
|
|
// Verify they are sorted by start time (oldest first)
|
|
expect(sessionCalls[0][0]).toContain('1. Oldest session');
|
|
expect(sessionCalls[1][0]).toContain('2. Middle session');
|
|
expect(sessionCalls[2][0]).toContain('3. Newest session');
|
|
});
|
|
|
|
it('should format session output with relative time and session ID', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'abc123def456',
|
|
file: 'session-file',
|
|
fileName: 'session-file.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Test message',
|
|
firstUserMessage: 'Test message',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await listSessions(mockConfig);
|
|
|
|
// Assert
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('1. Test message'),
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('some time ago'),
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('[abc123def456]'),
|
|
);
|
|
});
|
|
|
|
it('should handle single session', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'single-session',
|
|
file: 'session-file',
|
|
fileName: 'session-file.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Only session',
|
|
firstUserMessage: 'Only session',
|
|
isCurrentSession: true,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await listSessions(mockConfig);
|
|
|
|
// Assert
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
'\nAvailable sessions for this project (1):\n',
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('1. Only session'),
|
|
);
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining(', current)'),
|
|
);
|
|
});
|
|
|
|
it('should display summary as title when available instead of first user message', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-with-summary',
|
|
file: 'session-file',
|
|
fileName: 'session-file.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 10,
|
|
displayName: 'Add dark mode to the app', // Summary
|
|
firstUserMessage:
|
|
'How do I add dark mode to my React application with CSS variables?',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
summary: 'Add dark mode to the app',
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await listSessions(mockConfig);
|
|
|
|
// Assert: Should show the summary (displayName), not the first user message
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('1. Add dark mode to the app'),
|
|
);
|
|
expect(mocks.writeToStdout).not.toHaveBeenCalledWith(
|
|
expect.stringContaining('How do I add dark mode to my React application'),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('deleteSession', () => {
|
|
let mockConfig: Config;
|
|
let mockListSessions: ReturnType<typeof vi.fn>;
|
|
let mockDeleteSession: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
// Create mock config
|
|
mockConfig = {
|
|
storage: {
|
|
getProjectTempDir: vi.fn().mockReturnValue('/tmp/test-project'),
|
|
},
|
|
getSessionId: vi.fn().mockReturnValue('current-session-id'),
|
|
} as unknown as Config;
|
|
|
|
// Create mock methods
|
|
mockListSessions = vi.fn();
|
|
mockDeleteSession = vi.fn();
|
|
|
|
// Mock SessionSelector constructor
|
|
vi.mocked(SessionSelector).mockImplementation(
|
|
() =>
|
|
({
|
|
listSessions: mockListSessions,
|
|
}) as unknown as InstanceType<typeof SessionSelector>,
|
|
);
|
|
|
|
// Mock ChatRecordingService
|
|
vi.mocked(ChatRecordingService).mockImplementation(
|
|
() =>
|
|
({
|
|
deleteSession: mockDeleteSession,
|
|
}) as unknown as InstanceType<typeof ChatRecordingService>,
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should display error when no sessions are found', async () => {
|
|
// Arrange
|
|
mockListSessions.mockResolvedValue([]);
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, '1');
|
|
|
|
// Assert
|
|
expect(mockListSessions).toHaveBeenCalledOnce();
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'No sessions found for this project.',
|
|
);
|
|
expect(mockDeleteSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should delete session by UUID', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-uuid-123',
|
|
file: 'session-file-123',
|
|
fileName: 'session-file-123.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Test session',
|
|
firstUserMessage: 'Test session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
mockDeleteSession.mockImplementation(() => {});
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, 'session-uuid-123');
|
|
|
|
// Assert
|
|
expect(mockListSessions).toHaveBeenCalledOnce();
|
|
expect(mockDeleteSession).toHaveBeenCalledWith('session-file-123');
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
'Deleted session 1: Test session (some time ago)',
|
|
);
|
|
expect(mocks.writeToStderr).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should delete session by index', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
|
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-file-1',
|
|
fileName: 'session-file-1.json',
|
|
startTime: oneHourAgo.toISOString(),
|
|
lastUpdated: oneHourAgo.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'First session',
|
|
firstUserMessage: 'First session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
{
|
|
id: 'session-2',
|
|
file: 'session-file-2',
|
|
fileName: 'session-file-2.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 10,
|
|
displayName: 'Second session',
|
|
firstUserMessage: 'Second session',
|
|
isCurrentSession: false,
|
|
index: 2,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
mockDeleteSession.mockImplementation(() => {});
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, '2');
|
|
|
|
// Assert
|
|
expect(mockListSessions).toHaveBeenCalledOnce();
|
|
expect(mockDeleteSession).toHaveBeenCalledWith('session-file-2');
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
'Deleted session 2: Second session (some time ago)',
|
|
);
|
|
});
|
|
|
|
it('should display error for invalid session identifier (non-numeric)', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-file-1',
|
|
fileName: 'session-file-1.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Test session',
|
|
firstUserMessage: 'Test session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, 'invalid-id');
|
|
|
|
// Assert
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'Invalid session identifier "invalid-id". Use --list-sessions to see available sessions.',
|
|
);
|
|
expect(mockDeleteSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should display error for invalid session identifier (out of range)', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-file-1',
|
|
fileName: 'session-file-1.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Test session',
|
|
firstUserMessage: 'Test session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, '999');
|
|
|
|
// Assert
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'Invalid session identifier "999". Use --list-sessions to see available sessions.',
|
|
);
|
|
expect(mockDeleteSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should display error for invalid session identifier (zero)', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-file-1',
|
|
fileName: 'session-file-1.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Test session',
|
|
firstUserMessage: 'Test session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, '0');
|
|
|
|
// Assert
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'Invalid session identifier "0". Use --list-sessions to see available sessions.',
|
|
);
|
|
expect(mockDeleteSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should prevent deletion of current session', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'current-session-id',
|
|
file: 'current-session-file',
|
|
fileName: 'current-session-file.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Current session',
|
|
firstUserMessage: 'Current session',
|
|
isCurrentSession: true,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act - try to delete by index
|
|
await deleteSession(mockConfig, '1');
|
|
|
|
// Assert
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'Cannot delete the current active session.',
|
|
);
|
|
expect(mockDeleteSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should prevent deletion of current session by UUID', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'current-session-id',
|
|
file: 'current-session-file',
|
|
fileName: 'current-session-file.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Current session',
|
|
firstUserMessage: 'Current session',
|
|
isCurrentSession: true,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
|
|
// Act - try to delete by UUID
|
|
await deleteSession(mockConfig, 'current-session-id');
|
|
|
|
// Assert
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'Cannot delete the current active session.',
|
|
);
|
|
expect(mockDeleteSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle deletion errors gracefully', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-file-1',
|
|
fileName: 'session-file-1.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Test session',
|
|
firstUserMessage: 'Test session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
mockDeleteSession.mockImplementation(() => {
|
|
throw new Error('File deletion failed');
|
|
});
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, '1');
|
|
|
|
// Assert
|
|
expect(mockDeleteSession).toHaveBeenCalledWith('session-file-1');
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'Failed to delete session: File deletion failed',
|
|
);
|
|
});
|
|
|
|
it('should handle non-Error deletion failures', async () => {
|
|
// Arrange
|
|
const now = new Date('2025-01-20T12:00:00.000Z');
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-file-1',
|
|
fileName: 'session-file-1.json',
|
|
startTime: now.toISOString(),
|
|
lastUpdated: now.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Test session',
|
|
firstUserMessage: 'Test session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
mockDeleteSession.mockImplementation(() => {
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
throw 'Unknown error type';
|
|
});
|
|
|
|
// Act
|
|
await deleteSession(mockConfig, '1');
|
|
|
|
// Assert
|
|
expect(mocks.writeToStderr).toHaveBeenCalledWith(
|
|
'Failed to delete session: Unknown error',
|
|
);
|
|
});
|
|
|
|
it('should sort sessions before finding by index', async () => {
|
|
// Arrange: Create sessions in non-chronological order
|
|
const session1Time = new Date('2025-01-18T12:00:00.000Z');
|
|
const session2Time = new Date('2025-01-19T12:00:00.000Z');
|
|
const session3Time = new Date('2025-01-20T12:00:00.000Z');
|
|
|
|
const mockSessions: SessionInfo[] = [
|
|
{
|
|
id: 'session-3',
|
|
file: 'session-file-3',
|
|
fileName: 'session-file-3.json',
|
|
startTime: session3Time.toISOString(), // Newest
|
|
lastUpdated: session3Time.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Newest session',
|
|
firstUserMessage: 'Newest session',
|
|
isCurrentSession: false,
|
|
index: 3,
|
|
},
|
|
{
|
|
id: 'session-1',
|
|
file: 'session-file-1',
|
|
fileName: 'session-file-1.json',
|
|
startTime: session1Time.toISOString(), // Oldest
|
|
lastUpdated: session1Time.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Oldest session',
|
|
firstUserMessage: 'Oldest session',
|
|
isCurrentSession: false,
|
|
index: 1,
|
|
},
|
|
{
|
|
id: 'session-2',
|
|
file: 'session-file-2',
|
|
fileName: 'session-file-2.json',
|
|
startTime: session2Time.toISOString(), // Middle
|
|
lastUpdated: session2Time.toISOString(),
|
|
messageCount: 5,
|
|
displayName: 'Middle session',
|
|
firstUserMessage: 'Middle session',
|
|
isCurrentSession: false,
|
|
index: 2,
|
|
},
|
|
];
|
|
|
|
mockListSessions.mockResolvedValue(mockSessions);
|
|
mockDeleteSession.mockImplementation(() => {});
|
|
|
|
// Act - delete index 1 (should be oldest session after sorting)
|
|
await deleteSession(mockConfig, '1');
|
|
|
|
// Assert
|
|
expect(mockDeleteSession).toHaveBeenCalledWith('session-file-1');
|
|
expect(mocks.writeToStdout).toHaveBeenCalledWith(
|
|
expect.stringContaining('Oldest session'),
|
|
);
|
|
});
|
|
});
|