claude-harness/dsh/tests/openspec.test.mjs
Andrey Epifancev 8c0fc89bac fix(dsh): --openspec возвращает 1 при провале openspec init (#42)
Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
2026-10-24 19:52:36 +04:00

154 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, symlinkSync } from 'node:fs'
import { dirname, join } from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import { cleanup, tempDir, write } from './helpers.mjs'
const here = dirname(fileURLToPath(import.meta.url))
const script = join(here, '..', 'install.sh')
const root = join(here, '..', '..')
const agentsSkills = join(root, '.agents', 'skills')
const NAMES = [
'openspec-apply-change',
'openspec-archive-change',
'openspec-explore',
'openspec-propose',
'openspec-sync-specs',
'openspec-update-change',
]
test('repository ships six agents skills for DSH', () => {
assert.equal(readFileSync(join(agentsSkills, '.openspec-target'), 'utf8').trim(), 'agents')
for (const name of NAMES) {
const file = join(agentsSkills, name, 'SKILL.md')
assert.ok(existsSync(file), `${name}/SKILL.md is missing`)
const text = readFileSync(file, 'utf8')
assert.match(text, new RegExp(`^name: ${name}$`, 'm'), `${name}: frontmatter name must match the dir`)
assert.match(text, /description: .*openspec/i, `${name}: description must mention openspec`)
}
})
test('install.sh --check reports the openspec CLI and the agents skills', () => {
const home = tempDir()
try {
const bin = join(home, 'bin')
write(join(bin, 'openspec'), '#!/bin/sh\n[ "$1" = "--version" ] && echo 1.13.1\nexit 0\n')
chmodSync(join(bin, 'openspec'), 0o755)
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
const run = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.equal(run.status, 0, run.stderr)
assert.match(run.stdout, /ok: openspec 1\.13\.1/)
assert.match(run.stdout, /ok: openspec agents skills \(/)
} finally {
cleanup(home)
}
})
test('install.sh --check warns but does not fail without the openspec CLI', () => {
const home = tempDir()
try {
const bin = join(home, 'bin')
mkdirSync(bin, { recursive: true })
symlinkSync(process.execPath, join(bin, 'node'))
// openspec ставится в /opt/homebrew/bin; здесь только системные каталоги — CLI не найден
const env = {
...process.env,
HOME: home,
DSH_HOME: join(home, '.dsh'),
PATH: `${bin}:/usr/bin:/bin:/usr/sbin:/sbin`,
}
const run = spawnSync('bash', [script, '--check'], { env, encoding: 'utf8' })
assert.equal(run.status, 0, run.stderr)
assert.match(run.stdout + run.stderr, /WARN: openspec not found/)
} finally {
cleanup(home)
}
})
function fakeOpenspec(dir, log) {
const bin = join(dir, 'bin')
write(join(bin, 'openspec'), `#!/bin/sh\necho "$PWD $@" >> ${JSON.stringify(log)}\nexit 0\n`)
chmodSync(join(bin, 'openspec'), 0o755)
return bin
}
test('--openspec adds the agents adapter to an existing OpenSpec project', () => {
const home = tempDir()
try {
const project = join(home, 'project')
mkdirSync(join(project, 'openspec'), { recursive: true })
// macOS: os.tmpdir() идёт через симлинк /var -> /private/var, а $PWD в шелле — уже реальный путь
const realProject = realpathSync(project)
const log = join(home, 'calls.log')
const bin = fakeOpenspec(home, log)
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
assert.equal(run.status, 0, run.stderr)
assert.match(run.stdout, /ok: openspec agents adapter in /)
assert.match(readFileSync(log, 'utf8'), new RegExp(`^${realProject} init --tools agents$`, 'm'))
} finally {
cleanup(home)
}
})
test('--openspec refuses a project without an openspec root', () => {
const home = tempDir()
try {
const project = join(home, 'plain')
mkdirSync(project, { recursive: true })
const log = join(home, 'calls.log')
const bin = fakeOpenspec(home, log)
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
assert.notEqual(run.status, 0)
assert.match(run.stdout + run.stderr, /has no openspec\/ root/)
assert.equal(existsSync(log), false, 'openspec must not run in a non-OpenSpec project')
assert.equal(existsSync(join(project, 'openspec')), false)
} finally {
cleanup(home)
}
})
test('--openspec fails when openspec init fails', () => {
const home = tempDir()
try {
const project = join(home, 'project')
mkdirSync(join(project, 'openspec'), { recursive: true })
const bin = join(home, 'bin')
write(join(bin, 'openspec'), '#!/bin/sh\nexit 1\n')
chmodSync(join(bin, 'openspec'), 0o755)
const env = { ...process.env, HOME: home, DSH_HOME: join(home, '.dsh'), PATH: `${bin}:${process.env.PATH}` }
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
assert.notEqual(run.status, 0)
assert.doesNotMatch(run.stdout, /ok: openspec agents adapter/)
assert.match(run.stderr, /FAIL/)
} finally {
cleanup(home)
}
})
test('--openspec fails without the openspec CLI', () => {
const home = tempDir()
try {
const project = join(home, 'project')
mkdirSync(join(project, 'openspec'), { recursive: true })
const bin = join(home, 'bin')
mkdirSync(bin, { recursive: true })
symlinkSync(process.execPath, join(bin, 'node'))
// openspec ставится в /opt/homebrew/bin; здесь только системные каталоги — CLI не найден
const env = {
...process.env,
HOME: home,
DSH_HOME: join(home, '.dsh'),
PATH: `${bin}:/usr/bin:/bin:/usr/sbin:/sbin`,
}
const run = spawnSync('bash', [script, '--openspec'], { cwd: project, env, encoding: 'utf8' })
assert.notEqual(run.status, 0)
assert.match(run.stdout + run.stderr, /FAIL: openspec CLI not found/)
} finally {
cleanup(home)
}
})