skillhub/cli/test/integration/publish-dry-run.test.ts
dongmucat c1835fc9e9 merge(main): resolve CLI error mapping conflicts (#606)
Signed-off-by: dongmucat <1127093059@qq.com>
2026-07-29 10:24:23 +08:00

193 lines
6.8 KiB
TypeScript

import { mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, test } from 'bun:test'
import { createTempHome } from '../helpers/temp-env'
import { startFakeRegistry } from '../helpers/fake-registry'
import { runCli } from '../helpers/run-cli'
let registry: Awaited<ReturnType<typeof startFakeRegistry>> | undefined
afterEach(() => {
registry?.stop()
registry = undefined
})
async function login(env: { home: string }, registryUrl: string) {
const result = await runCli(['login', '--registry', registryUrl, '--token', 'sk_ok'], {
HOME: env.home,
USERPROFILE: env.home
})
if (result.exitCode !== 0) {
throw new Error(`login failed: ${result.stderr}`)
}
}
async function makeTempDir(...files: Array<[string, string]>) {
const dir = await mkdtemp(join(tmpdir(), 'skillhub-dryrun-'))
for (const [name, content] of files) {
await writeFile(join(dir, name), content)
}
return dir
}
describe('publish --dry-run', () => {
test('calls validate endpoint and reports success', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({ token: 'sk_ok' })
await login(env, registry.url)
const dir = await makeTempDir(['SKILL.md', '---\nname: my-skill\ndescription: A test\n---\n# Hello'])
const result = await runCli(['publish', dir, '--dry-run', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(result.exitCode).toBe(0)
expect(result.stdout).toContain('Validation passed')
expect(registry.received.validate).not.toBeNull()
expect(registry.received.validate!.namespace).toBe('global')
expect(registry.received.publish).toBeNull()
})
test('--dry-run with --json returns structured response on warnings (valid=false)', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({
token: 'sk_ok',
dryRunResponse: {
valid: false,
errors: [],
warnings: ['Disallowed file extension: data.bin'],
resolvedSlug: 'my-skill',
resolvedVersion: '2.0.0'
}
})
await login(env, registry.url)
const dir = await makeTempDir(['SKILL.md', '---\nname: my-skill\ndescription: test\n---\n'])
const result = await runCli(['publish', dir, '--dry-run', '--json', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(result.exitCode).toBe(6)
const json = JSON.parse(result.stdout)
expect(json.valid).toBe(false)
expect(json.resolvedSlug).toBe('my-skill')
expect(json.resolvedVersion).toBe('2.0.0')
expect(json.warnings).toContain('Disallowed file extension: data.bin')
})
test('--dry-run reports validation errors', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({
token: 'sk_ok',
dryRunResponse: {
valid: false,
errors: ['Missing required file: SKILL.md at root'],
warnings: [],
resolvedSlug: null,
resolvedVersion: null
}
})
await login(env, registry.url)
const dir = await makeTempDir(['README.md', '# No SKILL.md here'])
const result = await runCli(['publish', dir, '--dry-run', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(result.exitCode).toBe(6)
expect(result.stdout).toContain('Validation failed')
expect(result.stdout).toContain('Missing required file: SKILL.md at root')
})
test('--dry-run does not actually publish', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({ token: 'sk_ok' })
await login(env, registry.url)
const dir = await makeTempDir(['SKILL.md', '---\nname: test\ndescription: test\n---\n'])
await runCli(['publish', dir, '--dry-run', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(registry.received.publish).toBeNull()
})
test('--dry-run respects --namespace', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({ token: 'sk_ok' })
await login(env, registry.url)
const dir = await makeTempDir(['SKILL.md', '---\nname: test\ndescription: test\n---\n'])
await runCli(['publish', dir, '--dry-run', '--namespace', 'myteam', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(registry.received.validate!.namespace).toBe('myteam')
})
test('--dry-run forwards --visibility to server', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({ token: 'sk_ok' })
await login(env, registry.url)
const dir = await makeTempDir(['SKILL.md', '---\nname: test\ndescription: test\n---\n'])
await runCli(['publish', dir, '--dry-run', '--visibility', 'private', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(registry.received.validate!.visibility).toBe('PRIVATE')
})
test('--dry-run requires authentication', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({ token: 'sk_ok' })
const dir = await makeTempDir(['SKILL.md', '---\nname: test\ndescription: test\n---\n'])
const result = await runCli(['publish', dir, '--dry-run', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(result.exitCode).toBe(2)
expect(result.stderr).toContain('authentication')
})
test('--dry-run surfaces the public message and request ID on a structured 403', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({ token: 'sk_ok', failures: { validate: 'forbidden' } })
await login(env, registry.url)
const dir = await makeTempDir(['SKILL.md', '---\nname: test\ndescription: test\n---\n'])
const result = await runCli(['publish', dir, '--dry-run', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(result.exitCode).toBe(2)
expect(result.stderr).toContain('scope')
expect(result.stderr).toContain('Request ID: req-test-forbidden')
})
test('--dry-run uses a neutral fallback without leaking an unstructured 403 body', async () => {
const env = await createTempHome()
registry = await startFakeRegistry({ token: 'sk_ok', failures: { validate: 'forbidden_unstructured' } })
await login(env, registry.url)
const dir = await makeTempDir(['SKILL.md', '---\nname: test\ndescription: test\n---\n'])
const result = await runCli(['publish', dir, '--dry-run', '--registry', registry.url], {
HOME: env.home,
USERPROFILE: env.home
})
expect(result.exitCode).toBe(2)
expect(result.stderr).toContain('access denied')
expect(result.stderr).not.toContain('sensitive proxy denial')
})
})