Files
deepseek-harness/apps/desktop/tests/register-dsh.spec.ts
T

114 lines
4.8 KiB
TypeScript
Raw Normal View History

import { lstatSync, mkdirSync, mkdtempSync, readlinkSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { appendToUserPath, buildDshCmd, registerDshLauncher } from '../src/register-dsh.ts'
const dirs: string[] = []
/** A fake harness dir (with or without a `dsh` launcher) and a bin dir to register into. */
function fixtures(): { harnessDir: string; binDir: string } {
const root = mkdtempSync(join(tmpdir(), 'dsh-register-'))
dirs.push(root)
return { harnessDir: join(root, 'harness'), binDir: join(root, 'bin') }
}
afterEach(() => {
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true })
})
describe('registerDshLauncher', () => {
it('symlinks the harness launcher into the bin dir when absent', () => {
const { harnessDir, binDir } = fixtures()
mkdirSync(join(harnessDir, 'bin'), { recursive: true })
writeFileSync(join(harnessDir, 'dsh'), '#!/bin/sh\n')
expect(registerDshLauncher(harnessDir, binDir)).toEqual({
status: 'registered', path: join(binDir, 'dsh'), created: true,
})
expect(readlinkSync(join(binDir, 'dsh'))).toBe(join(harnessDir, 'dsh'))
})
it('is idempotent: confirms an existing link that already points at the launcher', () => {
const { harnessDir, binDir } = fixtures()
mkdirSync(harnessDir, { recursive: true })
mkdirSync(binDir, { recursive: true })
symlinkSync(join(harnessDir, 'dsh'), join(binDir, 'dsh'))
expect(registerDshLauncher(harnessDir, binDir)).toEqual({
status: 'registered', path: join(binDir, 'dsh'), created: false,
})
})
it('falls back to a user-writable dir when the primary is unwritable', () => {
const { harnessDir, binDir } = fixtures()
mkdirSync(harnessDir, { recursive: true })
writeFileSync(join(harnessDir, 'dsh'), '#!/bin/sh\n')
// A file occupies the primary's parent path, so creating the link there
// fails (ENOTDIR) and the fallback dir is used instead — mirroring a
// root-owned, non-writable /usr/local/bin.
mkdirSync(binDir, { recursive: true })
const fileBlocker = join(binDir, 'blocker')
writeFileSync(fileBlocker, '')
const result = registerDshLauncher(harnessDir, join(fileBlocker, 'nested'), join(binDir, 'fallback'), false)
if (result.status !== 'registered') {
throw new Error(`expected a registered link, got ${JSON.stringify(result)}`)
}
expect(result.created).toBe(true)
expect(readlinkSync(join(binDir, 'fallback', 'dsh'))).toBe(join(harnessDir, 'dsh'))
})
it('leaves an existing non-dsh entry untouched', () => {
const { harnessDir, binDir } = fixtures()
mkdirSync(harnessDir, { recursive: true })
mkdirSync(binDir, { recursive: true })
writeFileSync(join(binDir, 'dsh'), 'a real file, not ours\n')
expect(registerDshLauncher(harnessDir, binDir)).toEqual({
status: 'already-present', path: join(binDir, 'dsh'),
})
// The file is not replaced by a symlink and its content is untouched.
expect(lstatSync(join(binDir, 'dsh')).isSymbolicLink()).toBe(false)
expect(readFileSync(join(binDir, 'dsh'), 'utf8')).toBe('a real file, not ours\n')
})
it('leaves a symlink pointing elsewhere untouched', () => {
const { harnessDir, binDir } = fixtures()
mkdirSync(harnessDir, { recursive: true })
mkdirSync(binDir, { recursive: true })
symlinkSync('/elsewhere/dsh', join(binDir, 'dsh'))
expect(registerDshLauncher(harnessDir, binDir)).toEqual({
status: 'already-present', path: join(binDir, 'dsh'),
})
expect(readlinkSync(join(binDir, 'dsh'))).toBe('/elsewhere/dsh')
})
it('skips when the harness has no launcher (a development checkout)', () => {
const { harnessDir, binDir } = fixtures()
mkdirSync(harnessDir, { recursive: true })
expect(registerDshLauncher(harnessDir, binDir)).toEqual({ status: 'skipped' })
})
})
describe('buildDshCmd', () => {
it('runs the bundled node against the CLI entry with the caller arguments', () => {
expect(buildDshCmd()).toBe('@echo off\r\n"%~dp0bin\\node.exe" "%~dp0apps\\cli\\lib\\bin.js" %*\r\n')
})
})
describe('appendToUserPath', () => {
it('appends a missing directory', () => {
expect(appendToUserPath('C:\\a;C:\\b', 'C:\\harness'))
.toBe('C:\\a;C:\\b;C:\\harness')
})
it('is case-insensitive and idempotent for a present directory', () => {
expect(appendToUserPath('C:\\a;C:\\Harness', 'c:\\harness')).toBe('C:\\a;C:\\Harness')
})
it('appends to a trailing semicolon without doubling it', () => {
expect(appendToUserPath('C:\\a;', 'C:\\harness')).toBe('C:\\a;C:\\harness')
})
it('appends to an empty PATH', () => {
expect(appendToUserPath('', 'C:\\harness')).toBe(';C:\\harness')
})
})