test(windows): close native coverage gaps

This commit is contained in:
Tianyi Cui
2026-08-09 03:12:58 +08:00
parent 94799abfb0
commit bfb456e0c6
8 changed files with 63 additions and 5 deletions

View File

@@ -108,6 +108,7 @@ async function assertOwnerOnly(filename: string): Promise<void> {
return
}
/* v8 ignore stop */
/* v8 ignore start -- Windows has no POSIX mode enforcement; POSIX behavior tests enforce this peer. */
let mode: number
try {
mode = (await stat(filename)).mode
@@ -122,6 +123,7 @@ async function assertOwnerOnly(filename: string): Promise<void> {
`credentials-local: ${filename} is readable beyond its owner (mode ${(mode & 0o777).toString(8)});`
+ ` run "chmod 600 ${filename}" before starting again`,
)
/* v8 ignore stop */
}
/** Whether a filesystem error means absence; every non-ENOENT failure must surface. */

View File

@@ -96,6 +96,7 @@ type StubMode =
| 'send-error'
| 'prompt-after-idle'
| 'empty-page-after-latest'
| 'paged-scrollback'
class StubPtySession implements PtyBackendSession {
readonly motd = '__DSH_PERSISTENT_BASH_PROMPT__ '
@@ -211,6 +212,19 @@ class StubPtySession implements PtyBackendSession {
return { text: '', totalLines: 2, lineBegin: 1, lineEnd: 1, truncated: false }
}
const lines = this.scrollback.split('\n')
if (this.mode === 'paged-scrollback') {
const offset = request.offset ?? 0
const end = lines.length - offset
const start = Math.max(0, end - 3)
const returnedLines = end - start
return {
text: lines.slice(start, end).join('\n'),
totalLines: lines.length,
lineBegin: offset,
lineEnd: offset + returnedLines,
truncated: this.historyTruncated,
}
}
return {
text: this.scrollback,
totalLines: this.mode === 'empty-page-after-latest' ? lines.length + 1 : lines.length,
@@ -402,6 +416,16 @@ describe('tool-bash-persistent', () => {
expect(text(await call(ctx, owner, 'empty continuation page'))).toContain('hello from stub')
})
it('assembles retained output across backward scrollback pages', async () => {
const { ctx, owner, stub } = await setup({ backendType: 'stub', maxOutputChars: 1_000 })
await call(ctx, owner, 'warm up')
const session = stub.sessions[0]!
session.mode = 'paged-scrollback'
session.scrollback = ''
expect(text(await call(ctx, owner, 'paged output'))).toBe('hello from stub')
})
it('sanitizes a prompt fallback reached after multiple polling rounds', async () => {
const { ctx, owner, stub } = await setup({ backendType: 'stub', maxOutputChars: 1_000 })
await call(ctx, owner, 'warm up')

View File

@@ -137,6 +137,13 @@ describe('boot and reads', () => {
await expect(boot({ path, watch: false })).rejects.toThrow(/EACCES|permission/i)
})
it('fails loud when the document path names a directory', async () => {
const dir = await tempDir()
const path = join(dir, 'settings.yaml')
await mkdir(path)
await expect(boot({ path, watch: false })).rejects.toThrow(/EISDIR|directory/i)
})
it('fails loud on an unsupported extension', async () => {
const dir = await tempDir()
await expect(boot({ path: join(dir, 'settings.toml'), watch: false }))

View File

@@ -217,6 +217,13 @@ describe('sqlite backend specifics', () => {
await chmod(dir, 0o700)
})
it('propagates an invalid database filename before opening SQLite', async () => {
const path = await freshDbPath()
const backend = backendAt(`${path}\0invalid`)
await expect(backend.kv.open(DESCRIPTOR)).rejects.toThrow(/null bytes/i)
await backend.close()
})
it('preserves the mode of an existing database file', async () => {
if (process.platform === 'win32') return
const path = await freshDbPath()

View File

@@ -2,7 +2,7 @@ import { lstat, mkdir, mkdtemp, readFile, readdir, stat, symlink, writeFile } fr
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { writeFileAtomic } from '../src/index.ts'
import { withFileLock, writeFileAtomic } from '../src/index.ts'
async function scratch(): Promise<string> {
return mkdtemp(join(tmpdir(), 'dsh-atomic-write-'))
@@ -46,3 +46,17 @@ describe('writeFileAtomic', () => {
expect((await readdir(dir)).filter(entry => entry.includes('.tmp'))).toEqual([])
})
})
describe('withFileLock', () => {
it('rejects an invalid parent hierarchy before running the operation', async () => {
const dir = await scratch()
const parent = join(dir, 'not-a-directory')
await writeFile(parent, 'occupied')
let called = false
await expect(withFileLock(join(parent, 'document'), async () => {
called = true
})).rejects.toThrow(/ENOENT|ENOTDIR|not a directory/i)
expect(called).toBe(false)
})
})