| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 |
- // SDK 包与推送工具的 npm 仓库(/npm/):包信息、最新版本、包文件、作用域名称的两种写法、安全审计
- import { execFileSync } from 'node:child_process'
- import crypto from 'node:crypto'
- import fs from 'node:fs'
- import os from 'node:os'
- import path from 'node:path'
- import Fastify from 'fastify'
- import { afterAll, beforeAll, describe, expect, it } from 'vitest'
- import { NpmRegistry, registerNpmRegistry } from '../src/http/npm-registry'
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'npm-registry-'))
- const f = Fastify()
- function pack(manifest: Record<string, unknown>, file: string) {
- const stage = fs.mkdtempSync(path.join(os.tmpdir(), 'npm-registry-stage-'))
- fs.mkdirSync(path.join(stage, 'package'))
- fs.writeFileSync(path.join(stage, 'package', 'package.json'), JSON.stringify(manifest))
- fs.writeFileSync(path.join(stage, 'package', 'index.js'), 'export {}\n')
- execFileSync('tar', ['czf', path.join(dir, file), 'package'], { cwd: stage })
- }
- beforeAll(async () => {
- pack({ name: '@agent-server/client', version: '0.5.0' }, 'agent-server-client-0.5.0.tgz')
- pack({ name: '@agent-server/client', version: '0.10.0', peerDependencies: { '@agent-server/shared': '0.10.0' } }, 'agent-server-client-0.10.0.tgz')
- pack({ name: '@agent-server/skills-cli', version: '0.3.0', bin: { 'agent-skills': './agent-skills.mjs' } }, 'agent-server-skills-cli-0.3.0.tgz')
- fs.copyFileSync(path.join(dir, 'agent-server-skills-cli-0.3.0.tgz'), path.join(dir, 'agent-skills.tgz')) // 不带版本号的文件不算
- fs.writeFileSync(path.join(dir, 'broken-1.0.0.tgz'), 'not a tarball')
- registerNpmRegistry(f, new NpmRegistry(() => dir))
- await f.ready()
- })
- afterAll(() => f.close())
- describe('npm 仓库', () => {
- it('包信息:全部版本、最新版本按数字比较、对等依赖与 bin 原样保留、校验值与文件一致', async () => {
- for (const url of ['/npm/@agent-server%2fclient', '/npm/@agent-server%2Fclient', '/npm/@agent-server/client']) {
- const r = await f.inject({ url, headers: { host: 'agent.example.com' } })
- expect(r.statusCode).toBe(200)
- expect(r.headers['cache-control']).toBe('no-cache')
- const doc = r.json()
- expect(doc['dist-tags']).toEqual({ latest: '0.10.0' })
- expect(Object.keys(doc.versions).sort()).toEqual(['0.10.0', '0.5.0'])
- expect(doc.versions['0.10.0'].peerDependencies).toEqual({ '@agent-server/shared': '0.10.0' })
- expect(doc.versions['0.10.0'].dist.tarball).toBe('http://agent.example.com/npm/-/agent-server-client-0.10.0.tgz')
- }
- const cli = (await f.inject({ url: '/npm/@agent-server%2fskills-cli' })).json()
- expect(Object.keys(cli.versions)).toEqual(['0.3.0'])
- expect(cli.versions['0.3.0'].bin).toEqual({ 'agent-skills': './agent-skills.mjs' })
- const tgz = await f.inject({ url: '/npm/-/agent-server-skills-cli-0.3.0.tgz' })
- expect(tgz.statusCode).toBe(200)
- expect(tgz.headers['cache-control']).toContain('immutable')
- expect(cli.versions['0.3.0'].dist.integrity).toBe(`sha512-${crypto.createHash('sha512').update(tgz.rawPayload).digest('base64')}`)
- })
- it('不存在的包与文件为 404;旧的固定文件名不提供;安全审计返回空结果;最新版本列表', async () => {
- expect((await f.inject({ url: '/npm/@agent-server%2fnope' })).statusCode).toBe(404)
- expect((await f.inject({ url: '/npm/-/agent-skills.tgz' })).statusCode).toBe(404)
- expect((await f.inject({ url: '/npm/-/..%2f..%2fetc%2fpasswd' })).statusCode).toBe(404)
- expect((await f.inject({ method: 'POST', url: '/npm/-/npm/v1/security/advisories/bulk', payload: {} })).json()).toEqual({})
- expect((await f.inject({ url: '/npm/-/latest' })).json()).toEqual(expect.arrayContaining([
- { name: '@agent-server/client', version: '0.10.0', bin: false },
- { name: '@agent-server/skills-cli', version: '0.3.0', bin: true },
- ]))
- })
- })
|