test(windows): close native coverage gaps
This commit is contained in:
@@ -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. */
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 }))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user