Fix file browser absolute path copy (#860)
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getClipboardPathForEntry } from '@/utils/file-path'
|
||||
|
||||
const baseEntry = {
|
||||
name: 'app.log',
|
||||
path: 'logs/app.log',
|
||||
isDir: false,
|
||||
size: 12,
|
||||
modTime: '2026-05-20T00:00:00.000Z',
|
||||
}
|
||||
|
||||
describe('file path clipboard helpers', () => {
|
||||
it('prefers absolute path metadata when available', () => {
|
||||
expect(getClipboardPathForEntry({
|
||||
...baseEntry,
|
||||
absolutePath: '/home/agent/.hermes/logs/app.log',
|
||||
})).toBe('/home/agent/.hermes/logs/app.log')
|
||||
})
|
||||
|
||||
it('falls back to the relative operation path for older API responses', () => {
|
||||
expect(getClipboardPathForEntry(baseEntry)).toBe('logs/app.log')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const provider = {
|
||||
listDir: vi.fn(),
|
||||
stat: vi.fn(),
|
||||
}
|
||||
const createFileProviderMock = vi.fn(async () => provider)
|
||||
const resolveHermesPathMock = vi.fn((relativePath: string) => {
|
||||
const normalized = relativePath.replace(/^\/+/, '')
|
||||
return normalized ? `/home/agent/.hermes/${normalized}` : '/home/agent/.hermes'
|
||||
})
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/file-provider', () => ({
|
||||
createFileProvider: createFileProviderMock,
|
||||
resolveHermesPath: resolveHermesPathMock,
|
||||
isSensitivePath: vi.fn(() => false),
|
||||
MAX_EDIT_SIZE: 10 * 1024 * 1024,
|
||||
}))
|
||||
|
||||
describe('file routes path metadata', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
createFileProviderMock.mockClear()
|
||||
resolveHermesPathMock.mockClear()
|
||||
provider.listDir.mockReset()
|
||||
provider.stat.mockReset()
|
||||
})
|
||||
|
||||
it('returns absolute paths for listed entries while preserving relative operation paths', async () => {
|
||||
provider.listDir.mockResolvedValue([
|
||||
{ name: 'app.log', path: 'logs/app.log', isDir: false, size: 12, modTime: '2026-05-20T00:00:00.000Z' },
|
||||
])
|
||||
|
||||
const { fileRoutes } = await import('../../packages/server/src/routes/hermes/files')
|
||||
const layer = fileRoutes.stack.find((entry: any) => entry.path === '/api/hermes/files/list')
|
||||
const ctx: any = { query: { path: 'logs' }, body: null }
|
||||
|
||||
await layer.stack[0](ctx)
|
||||
|
||||
expect(provider.listDir).toHaveBeenCalledWith('/home/agent/.hermes/logs')
|
||||
expect(ctx.body).toEqual({
|
||||
path: 'logs',
|
||||
absolutePath: '/home/agent/.hermes/logs',
|
||||
entries: [
|
||||
{
|
||||
name: 'app.log',
|
||||
path: 'logs/app.log',
|
||||
absolutePath: '/home/agent/.hermes/logs/app.log',
|
||||
isDir: false,
|
||||
size: 12,
|
||||
modTime: '2026-05-20T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('returns an absolute path in stat responses', async () => {
|
||||
provider.stat.mockResolvedValue({
|
||||
name: 'app.log',
|
||||
path: 'logs/app.log',
|
||||
isDir: false,
|
||||
size: 12,
|
||||
modTime: '2026-05-20T00:00:00.000Z',
|
||||
})
|
||||
|
||||
const { fileRoutes } = await import('../../packages/server/src/routes/hermes/files')
|
||||
const layer = fileRoutes.stack.find((entry: any) => entry.path === '/api/hermes/files/stat')
|
||||
const ctx: any = { query: { path: 'logs/app.log' }, body: null }
|
||||
|
||||
await layer.stack[0](ctx)
|
||||
|
||||
expect(ctx.body).toEqual({
|
||||
name: 'app.log',
|
||||
path: 'logs/app.log',
|
||||
absolutePath: '/home/agent/.hermes/logs/app.log',
|
||||
isDir: false,
|
||||
size: 12,
|
||||
modTime: '2026-05-20T00:00:00.000Z',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
import { mkdtempSync, mkdirSync, rmSync } from 'fs'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { resolveTerminalCwd } from '../../packages/server/src/routes/hermes/terminal'
|
||||
|
||||
const tmpRoots: string[] = []
|
||||
|
||||
function makeTmpRoot() {
|
||||
const root = mkdtempSync(join(tmpdir(), 'wui-terminal-cwd-'))
|
||||
tmpRoots.push(root)
|
||||
return root
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const root of tmpRoots.splice(0)) rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('terminal cwd resolution', () => {
|
||||
it('defaults terminal sessions to the active Hermes profile directory', () => {
|
||||
const profileDir = makeTmpRoot()
|
||||
expect(resolveTerminalCwd({}, profileDir)).toBe(profileDir)
|
||||
})
|
||||
|
||||
it('resolves relative configured cwd from the Hermes profile directory', () => {
|
||||
const profileDir = makeTmpRoot()
|
||||
mkdirSync(join(profileDir, 'workspace'))
|
||||
expect(resolveTerminalCwd({ cwd: 'workspace' }, profileDir)).toBe(join(profileDir, 'workspace'))
|
||||
})
|
||||
|
||||
it('uses absolute configured cwd when it exists', () => {
|
||||
const profileDir = makeTmpRoot()
|
||||
const cwd = makeTmpRoot()
|
||||
expect(resolveTerminalCwd({ cwd }, profileDir)).toBe(cwd)
|
||||
})
|
||||
|
||||
it('falls back to the profile directory when configured cwd is missing', () => {
|
||||
const profileDir = makeTmpRoot()
|
||||
expect(resolveTerminalCwd({ cwd: 'missing' }, profileDir)).toBe(profileDir)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user