diff --git a/packages/cli/src/ui/components/SessionSummaryDisplay.test.tsx b/packages/cli/src/ui/components/SessionSummaryDisplay.test.tsx index a0ebcd2433..2ed71762b7 100644 --- a/packages/cli/src/ui/components/SessionSummaryDisplay.test.tsx +++ b/packages/cli/src/ui/components/SessionSummaryDisplay.test.tsx @@ -5,11 +5,23 @@ */ import { renderWithProviders } from '../../test-utils/render.js'; -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SessionSummaryDisplay } from './SessionSummaryDisplay.js'; import * as SessionContext from '../contexts/SessionContext.js'; import type { SessionMetrics } from '../contexts/SessionContext.js'; -import { ToolCallDecision } from '@google/gemini-cli-core'; +import { + ToolCallDecision, + getShellConfiguration, +} from '@google/gemini-cli-core'; + +vi.mock('@google/gemini-cli-core', async (importOriginal) => { + const actual = + await importOriginal(); + return { + ...actual, + getShellConfiguration: vi.fn(), + }; +}); vi.mock('../contexts/SessionContext.js', async (importOriginal) => { const actual = await importOriginal(); @@ -19,6 +31,7 @@ vi.mock('../contexts/SessionContext.js', async (importOriginal) => { }; }); +const getShellConfigurationMock = vi.mocked(getShellConfiguration); const useSessionStatsMock = vi.mocked(SessionContext.useSessionStats); const renderWithMockedStats = async ( @@ -70,6 +83,14 @@ describe('', () => { }, }; + beforeEach(() => { + getShellConfigurationMock.mockReturnValue({ + executable: 'bash', + argsPrefix: ['-c'], + shell: 'bash', + }); + }); + it('renders the summary display with a title', async () => { const metrics: SessionMetrics = { ...emptyMetrics, @@ -102,29 +123,69 @@ describe('', () => { unmount(); }); - it('renders a standard UUID-formatted session ID in the footer', async () => { - const uuidSessionId = '1234-abcd-5678-efgh'; - const { lastFrame, unmount } = await renderWithMockedStats( - emptyMetrics, - uuidSessionId, - ); - const output = lastFrame(); + describe('Session ID escaping', () => { + it('renders a standard UUID-formatted session ID in the footer (bash)', async () => { + const uuidSessionId = '1234-abcd-5678-efgh'; + const { lastFrame, unmount } = await renderWithMockedStats( + emptyMetrics, + uuidSessionId, + ); + const output = lastFrame(); - // Standard UUID characters should not be escaped/quoted by default for bash. - expect(output).toContain('gemini --resume 1234-abcd-5678-efgh'); - unmount(); - }); + // Standard UUID characters should not be escaped/quoted by default for bash. + expect(output).toContain('gemini --resume 1234-abcd-5678-efgh'); + unmount(); + }); - it('sanitizes a malicious session ID in the footer', async () => { - const maliciousSessionId = "'; rm -rf / #"; - const { lastFrame, unmount } = await renderWithMockedStats( - emptyMetrics, - maliciousSessionId, - ); - const output = lastFrame(); + it('sanitizes a malicious session ID in the footer (bash)', async () => { + const maliciousSessionId = "'; rm -rf / #"; + const { lastFrame, unmount } = await renderWithMockedStats( + emptyMetrics, + maliciousSessionId, + ); + const output = lastFrame(); - // escapeShellArg (using shell-quote for bash) will wrap special characters in double quotes. - expect(output).toContain('gemini --resume "\'; rm -rf / #"'); - unmount(); + // escapeShellArg (using shell-quote for bash) will wrap special characters in double quotes. + expect(output).toContain('gemini --resume "\'; rm -rf / #"'); + unmount(); + }); + + it('renders a standard UUID-formatted session ID in the footer (powershell)', async () => { + getShellConfigurationMock.mockReturnValue({ + executable: 'powershell.exe', + argsPrefix: ['-NoProfile', '-Command'], + shell: 'powershell', + }); + + const uuidSessionId = '1234-abcd-5678-efgh'; + const { lastFrame, unmount } = await renderWithMockedStats( + emptyMetrics, + uuidSessionId, + ); + const output = lastFrame(); + + // PowerShell wraps strings in single quotes + expect(output).toContain("gemini --resume '1234-abcd-5678-efgh'"); + unmount(); + }); + + it('sanitizes a malicious session ID in the footer (powershell)', async () => { + getShellConfigurationMock.mockReturnValue({ + executable: 'powershell.exe', + argsPrefix: ['-NoProfile', '-Command'], + shell: 'powershell', + }); + + const maliciousSessionId = "'; rm -rf / #"; + const { lastFrame, unmount } = await renderWithMockedStats( + emptyMetrics, + maliciousSessionId, + ); + const output = lastFrame(); + + // PowerShell wraps in single quotes and escapes internal single quotes by doubling them + expect(output).toContain("gemini --resume '''; rm -rf / #'"); + unmount(); + }); }); });