From 9d08ca4e43ad8b5bc8ffdec5e6475fecca6cf5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 3 Jul 2026 16:43:33 +0800 Subject: [PATCH] test: add skill market contracts --- src/server/__tests__/fixtures/skill-market.ts | 68 +++++++++++++++++ src/server/__tests__/skill-market.test.ts | 23 ++++++ src/server/services/skillMarket/types.ts | 73 +++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 src/server/__tests__/fixtures/skill-market.ts create mode 100644 src/server/__tests__/skill-market.test.ts create mode 100644 src/server/services/skillMarket/types.ts diff --git a/src/server/__tests__/fixtures/skill-market.ts b/src/server/__tests__/fixtures/skill-market.ts new file mode 100644 index 00000000..0f92a0d5 --- /dev/null +++ b/src/server/__tests__/fixtures/skill-market.ts @@ -0,0 +1,68 @@ +export const CLAWHUB_TOP_SKILLS_RESPONSE = { + items: [ + { + slug: 'skill-vetter', + displayName: 'Skill Vetter', + summary: 'Security-first skill vetting for AI agents.', + description: null, + topics: ['GitHub', 'Permission'], + tags: { latest: '1.0.0' }, + stats: { comments: 0, downloads: 260911, installs: 11988, stars: 1248, versions: 1 }, + latestVersion: { version: '1.0.0', license: 'Apache-2.0' }, + metadata: null, + }, + ], + nextCursor: null, +} + +export const CLAWHUB_SCAN_RESPONSE = { + status: 'clean', + hasWarnings: false, + scanners: { + skillspector: { status: 'clean', summary: 'No dangerous patterns detected.' }, + }, + sha256: 'a'.repeat(64), +} + +export const SKILLHUB_TOP_SKILLS_RESPONSE = { + code: 0, + data: { + skills: [ + { + slug: 'skill-vetter', + name: 'Skill Vetter', + description: 'Security-first skill vetting for AI agents.', + description_zh: 'AI智能体技能安全预审工具。', + downloads: 273329, + installs: 37273, + stars: 1253, + ownerName: 'spclaudehome', + source: 'clawhub', + upstream_url: 'https://clawhub.ai/spclaudehome/skill-vetter', + version: '1.0.0', + labels: { requires_api_key: 'false' }, + verified: false, + }, + ], + }, +} + +export const SKILLHUB_DETAIL_RESPONSE = { + contentZhAvailable: true, + latestVersion: { version: '1.0.0' }, + owner: { handle: 'spclaudehome', displayName: 'spclaudehome' }, + securityReports: { + keen: { status: 'benign', statusText: '安全,无风险' }, + sanbu: { status: 'benign', statusText: '安全,无风险' }, + }, + skill: { + slug: 'skill-vetter', + displayName: 'Skill Vetter', + summary: 'Security-first skill vetting for AI agents.', + summary_zh: 'AI智能体技能安全预审工具。', + sourceUrl: 'https://clawhub.ai/spclaudehome/skill-vetter', + stats: { downloads: 273329, installs: 37273, stars: 1253 }, + labels: { requires_api_key: 'false' }, + version: '1.0.0', + }, +} diff --git a/src/server/__tests__/skill-market.test.ts b/src/server/__tests__/skill-market.test.ts new file mode 100644 index 00000000..bd1ce956 --- /dev/null +++ b/src/server/__tests__/skill-market.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'bun:test' +import { + CLAWHUB_TOP_SKILLS_RESPONSE, + SKILLHUB_TOP_SKILLS_RESPONSE, +} from './fixtures/skill-market.js' + +describe('skill market fixtures', () => { + it('keeps representative ClawHub fixture shape stable', () => { + expect(CLAWHUB_TOP_SKILLS_RESPONSE.items[0]).toMatchObject({ + slug: 'skill-vetter', + displayName: 'Skill Vetter', + stats: expect.objectContaining({ downloads: expect.any(Number) }), + }) + }) + + it('keeps representative SkillHub fixture shape stable', () => { + expect(SKILLHUB_TOP_SKILLS_RESPONSE.data.skills[0]).toMatchObject({ + slug: 'skill-vetter', + source: 'clawhub', + labels: expect.objectContaining({ requires_api_key: 'false' }), + }) + }) +}) diff --git a/src/server/services/skillMarket/types.ts b/src/server/services/skillMarket/types.ts new file mode 100644 index 00000000..3a2d8f2d --- /dev/null +++ b/src/server/services/skillMarket/types.ts @@ -0,0 +1,73 @@ +export type SkillMarketSource = 'clawhub' | 'skillhub' | 'anthropic-github' +export type SkillMarketSourceMode = 'primary' | 'fallback' | 'enhanced' + +export type SkillMarketTrustState = + | 'clean' + | 'benign' + | 'signed' + | 'official' + | 'warning' + | 'unknown' + | 'blocked' + +export type SkillMarketRiskLabel = + | 'allowed-tools' + | 'hooks' + | 'scripts' + | 'executables' + | 'external-network' + | 'requires-api-key' + +export type SkillMarketItem = { + source: SkillMarketSource + sourceMode: SkillMarketSourceMode + slug: string + displayName: string + summary: string + summaryZh?: string + owner?: string + canonicalUrl: string + upstreamUrl?: string + license?: string | null + version?: string + downloads?: number + installs?: number + stars?: number + category?: string + tags?: string[] + requiresApiKey?: boolean + trustState: SkillMarketTrustState + trustSummary?: string + installed: boolean +} + +export type SkillMarketFile = { + path: string + size?: number + sha256?: string +} + +export type SkillMarketDetail = SkillMarketItem & { + files: SkillMarketFile[] + entryPreview?: string + riskLabels: SkillMarketRiskLabel[] + installEligibility: + | { status: 'installable' } + | { status: 'installed'; installedSkillName: string } + | { status: 'conflict'; targetPath: string } + | { status: 'blocked'; reason: string } +} + +export type SkillMarketListResult = { + items: SkillMarketItem[] + nextCursor: string | null + source: SkillMarketSource + sourceStatus: 'ok' | 'fallback' | 'cached' + message?: string +} + +export type SkillMarketInstallResult = { + installed: true + skillName: string + targetPath: string +}