Files
dify/cli/scripts/install-r2.ps1.test.ts
fd2db63184 feat: KnowledgeFS and New Knowledge (#44165)
Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com>
Co-authored-by: hjlarry <hjlarry@163.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: zhaohao1004 <zhaohao1004@gmail.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: FFXN <lizy@dify.ai>
2026-10-23 03:34:59 +00:00

140 lines
5.2 KiB
TypeScript

import { spawnSync } from 'node:child_process'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it, vi } from 'vite-plus/test'
const SCRIPT = fileURLToPath(new URL('./install-r2.ps1', import.meta.url))
const hasPwsh = spawnSync('pwsh', ['-v'], { encoding: 'utf8' }).status === 0
// Each test starts pwsh; cold starts on a busy CI runner can exceed the default 5 seconds.
if (hasPwsh) vi.setConfig({ testTimeout: 15_000 })
const d = hasPwsh ? describe : describe.skip
const MANIFEST = JSON.stringify({
schema: 1,
name: 'difyctl',
channel: 'edge',
version: '0.1.0-edge.2fd7b82',
baseUrl: 'https://pub.example.r2.dev/difyctl/edge/0.1.0-edge.2fd7b82',
targets: {
'windows-x64': { asset: 'difyctl-v0.1.0-edge.2fd7b82-windows-x64.exe', sha256: 'deadbeef' },
},
})
function pwsh(
program: string,
env: Record<string, string> = {},
): { code: number; stdout: string; stderr: string } {
const full = `. '${SCRIPT}'\n${program}`
const r = spawnSync('pwsh', ['-NoProfile', '-Command', full], {
encoding: 'utf8',
env: { ...process.env, DIFYCTL_INSTALL_LIB: '1', ...env },
})
return {
code: r.status ?? 1,
stdout: (r.stdout ?? '').replace(/\r\n/g, '\n').trim(),
stderr: r.stderr ?? '',
}
}
d('install-r2.ps1', () => {
it('parses a target asset + sha from the manifest', () => {
const prog =
`$m = ConvertFrom-Json @'\n${MANIFEST}\n'@\n` +
`Write-Output (Get-TargetField $m 'windows-x64' 'asset')\n` +
`Write-Output (Get-TargetField $m 'windows-x64' 'sha256')`
const { stdout } = pwsh(prog)
expect(stdout).toBe('difyctl-v0.1.0-edge.2fd7b82-windows-x64.exe\ndeadbeef')
})
it('errors when DIFYCTL_R2_BASE is unset', () => {
const r = spawnSync('pwsh', ['-NoProfile', '-File', SCRIPT], {
encoding: 'utf8',
env: { ...process.env, DIFYCTL_R2_BASE: '' },
})
if (hasPwsh) {
expect(r.status).not.toBe(0)
expect(r.stderr + r.stdout).toMatch(/DIFYCTL_R2_BASE/)
}
})
})
d('install-r2.ps1 install location, PATH hint and download', () => {
const where = (env: Record<string, string>) =>
pwsh('Write-Output (Resolve-BinDir)', {
DIFYCTL_PREFIX: '',
DIFYCTL_INSTALL_DIR: '',
LOCALAPPDATA: '/lad',
...env,
}).stdout
it('defaults to %LOCALAPPDATA%\\difyctl\\bin', () => {
expect(where({})).toBe(join('/lad', 'difyctl', 'bin'))
})
it('accepts DIFYCTL_PREFIX like the GitHub installer', () => {
expect(where({ DIFYCTL_PREFIX: '/opt/dify' })).toBe(join('/opt/dify', 'bin'))
})
it('keeps DIFYCTL_INSTALL_DIR as the bin folder itself, over DIFYCTL_PREFIX', () => {
expect(where({ DIFYCTL_PREFIX: '/opt/dify', DIFYCTL_INSTALL_DIR: '/custom/bin' })).toBe(
'/custom/bin',
)
})
it('the PATH command adds to the user PATH, not the whole session PATH', () => {
const hint = pwsh(`Write-Output (Get-PathHint 'C:\\dify\\bin')`).stdout
expect(hint).toContain(
`"C:\\dify\\bin;" + [Environment]::GetEnvironmentVariable('PATH', 'User')`,
)
expect(hint).not.toContain('$env:PATH')
})
it('stops before downloading when the checksum is missing', () => {
const r = pwsh(
"function Invoke-WebRequest { throw 'must not download' }\n" +
"try { Install-DifyctlBinary 'https://x/difyctl.exe' '' '1.0.0' 'edge' '/tmp/none' } " +
'catch { Write-Output $_.Exception.Message }',
)
expect(r.stdout).toMatch(/^no checksum published for https:\/\/x\/difyctl\.exe/)
})
it('downloads to a unique temp file, not one named after the asset', () => {
const r = pwsh(
'function Invoke-WebRequest { param($Uri, $OutFile) Write-Output "OUT=$OutFile"; Set-Content -Path $OutFile -Value x }\n' +
"try { Install-DifyctlBinary 'https://x/difyctl.exe' 'deadbeef' '1.0.0' 'edge' '/tmp/none' } " +
'catch { Write-Output $_.Exception.Message }',
)
expect(r.stdout).toMatch(/OUT=.*difyctl-[0-9a-f]{32}\.exe\n/)
expect(r.stdout).toMatch(/checksum mismatch/)
})
})
// A v2 binary answers `install skills --help`; a v1 one exits 1 with "unknown command".
// The fakes are shell scripts, so this runs where pwsh runs outside Windows.
describe.skipIf(!hasPwsh || process.platform === 'win32')('install-r2.ps1 Write-SkillHint', () => {
const hint = (bin: string) => pwsh(`Write-SkillHint '${bin}'`)
it('prints the hint for a binary that has install skills, and stays quiet otherwise', () => {
const dir = mkdtempSync(join(tmpdir(), 'difyctl-hint-'))
try {
const v2 = join(dir, 'v2')
const v1 = join(dir, 'v1')
writeFileSync(v2, '#!/bin/sh\n[ "$*" = "install skills --help" ]\n', { mode: 0o755 })
writeFileSync(v1, '#!/bin/sh\necho "unknown command: $*" >&2\nexit 1\n', { mode: 0o755 })
expect(hint(v2)).toMatchObject({
code: 0,
stdout: expect.stringContaining('difyctl install skills <skills root>'),
})
expect(hint(v1)).toMatchObject({ code: 0, stdout: '' })
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
it('prints nothing when the binary cannot run', () => {
expect(hint('/nonexistent/difyctl')).toMatchObject({ code: 0, stdout: '' })
})
})