@@ -4,6 +4,31 @@ vi.mock('ofetch', () => ({
44 ofetch : vi . fn ( ) ,
55} ) )
66
7+ function mockResolveAndDetail ( ofetch : ReturnType < typeof vi . fn > , opts : {
8+ packageName : string
9+ owner ?: string
10+ repo ?: string
11+ raw ?: string
12+ pushedAt ?: string
13+ } ) {
14+ const owner = opts . owner ?? 'antfu'
15+ const repo = opts . repo ?? 'skills'
16+ ofetch . mockResolvedValueOnce ( {
17+ [ opts . packageName ] : { owner, repo, official : false } ,
18+ } )
19+ ofetch . mockResolvedValueOnce ( {
20+ owner,
21+ repo,
22+ name : opts . packageName ,
23+ displayName : opts . packageName ,
24+ installs : 1 ,
25+ branch : 'main' ,
26+ skillPath : `skills/${ opts . packageName } /SKILL.md` ,
27+ raw : opts . raw ?? '# skill' ,
28+ pushedAt : opts . pushedAt ?? '2026-01-01T00:00:00Z' ,
29+ } )
30+ }
31+
732describe ( 'registry client' , ( ) => {
833 let originalUrl : string | undefined
934
@@ -22,52 +47,105 @@ describe('registry client', () => {
2247 it ( 'fetchRegistrySkill uses default base when env unset' , async ( ) => {
2348 delete process . env . SKILLD_REGISTRY_URL
2449 const { ofetch } = await import ( 'ofetch' )
25- vi . mocked ( ofetch ) . mockResolvedValueOnce ( { name : 'vue-skilld' , packageName : 'vue' , version : '3.5.0' , content : '# vue' } )
50+ mockResolveAndDetail ( vi . mocked ( ofetch ) , { packageName : 'vue' } )
2651
2752 const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
28- await fetchRegistrySkill ( 'vue' )
29-
30- expect ( ofetch ) . toHaveBeenCalledWith ( 'https://skilld.dev/api/skills/vue' )
53+ const skill = await fetchRegistrySkill ( 'vue' )
54+
55+ expect ( skill ) . not . toBeNull ( )
56+ expect ( ofetch ) . toHaveBeenNthCalledWith ( 1 , 'https://skilld.dev/api/skills/resolve' , {
57+ method : 'POST' ,
58+ body : { items : [ { packageName : 'vue' , owner : undefined } ] } ,
59+ } )
60+ expect ( ofetch ) . toHaveBeenNthCalledWith ( 2 , 'https://skilld.dev/api/skills/antfu/skills/vue' )
3161 } )
3262
3363 it ( 'fetchRegistrySkill respects SKILLD_REGISTRY_URL override' , async ( ) => {
3464 process . env . SKILLD_REGISTRY_URL = 'http://localhost:3000/api'
3565 const { ofetch } = await import ( 'ofetch' )
36- vi . mocked ( ofetch ) . mockResolvedValueOnce ( { name : 'vue-skilld' , packageName : 'vue' , version : '3.5.0' , content : '# vue' } )
66+ mockResolveAndDetail ( vi . mocked ( ofetch ) , { packageName : 'vue' } )
3767
3868 const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
3969 await fetchRegistrySkill ( 'vue' )
4070
41- expect ( ofetch ) . toHaveBeenCalledWith ( 'http://localhost:3000/api/skills/vue' )
71+ expect ( ofetch ) . toHaveBeenNthCalledWith ( 1 , 'http://localhost:3000/api/skills/resolve' , expect . any ( Object ) )
72+ expect ( ofetch ) . toHaveBeenNthCalledWith ( 2 , 'http://localhost:3000/api/skills/antfu/skills/vue' )
4273 } )
4374
4475 it ( 'strips trailing slash from override' , async ( ) => {
4576 process . env . SKILLD_REGISTRY_URL = 'http://localhost:3000/api/'
4677 const { ofetch } = await import ( 'ofetch' )
47- vi . mocked ( ofetch ) . mockResolvedValueOnce ( { name : 'vue-skilld' , packageName : 'vue' , version : '3.5.0' , content : '# vue' } )
78+ mockResolveAndDetail ( vi . mocked ( ofetch ) , { packageName : 'vue' } )
4879
4980 const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
5081 await fetchRegistrySkill ( 'vue' )
5182
52- expect ( ofetch ) . toHaveBeenCalledWith ( 'http://localhost:3000/api/skills/vue' )
83+ expect ( ofetch ) . toHaveBeenNthCalledWith ( 1 , 'http://localhost:3000/api/skills/resolve' , expect . any ( Object ) )
84+ } )
85+
86+ it ( 'returns null when resolve fails' , async ( ) => {
87+ const { ofetch } = await import ( 'ofetch' )
88+ vi . mocked ( ofetch ) . mockRejectedValueOnce ( new Error ( 'network' ) )
89+
90+ const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
91+ expect ( await fetchRegistrySkill ( 'nonexistent' ) ) . toBeNull ( )
5392 } )
5493
55- it ( 'returns null when registry fetch fails ' , async ( ) => {
94+ it ( 'returns null when resolve has no hit ' , async ( ) => {
5695 const { ofetch } = await import ( 'ofetch' )
57- vi . mocked ( ofetch ) . mockRejectedValueOnce ( new Error ( '404' ) )
96+ vi . mocked ( ofetch ) . mockResolvedValueOnce ( { } )
5897
5998 const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
6099 expect ( await fetchRegistrySkill ( 'nonexistent' ) ) . toBeNull ( )
100+ expect ( ofetch ) . toHaveBeenCalledTimes ( 1 )
61101 } )
62102
63- it ( 'encodes scoped package names' , async ( ) => {
103+ it ( 'returns null when detail has no raw SKILL.md' , async ( ) => {
104+ const { ofetch } = await import ( 'ofetch' )
105+ vi . mocked ( ofetch )
106+ . mockResolvedValueOnce ( { vue : { owner : 'antfu' , repo : 'skills' , official : false } } )
107+ . mockResolvedValueOnce ( { owner : 'antfu' , repo : 'skills' , name : 'vue' , raw : null } )
108+
109+ const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
110+ expect ( await fetchRegistrySkill ( 'vue' ) ) . toBeNull ( )
111+ } )
112+
113+ it ( 'passes scoped package names unencoded to resolve body' , async ( ) => {
64114 delete process . env . SKILLD_REGISTRY_URL
65115 const { ofetch } = await import ( 'ofetch' )
66- vi . mocked ( ofetch ) . mockResolvedValueOnce ( { name : 'nuxt-ui-skilld' , packageName : '@nuxt/ui' , version : '3.0.0 ' , content : '# nuxt/ ui' } )
116+ mockResolveAndDetail ( vi . mocked ( ofetch ) , { packageName : '@nuxt/ui' , owner : 'nuxt ' , repo : 'ui' } )
67117
68118 const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
69119 await fetchRegistrySkill ( '@nuxt/ui' )
70120
71- expect ( ofetch ) . toHaveBeenCalledWith ( 'https://skilld.dev/api/skills/%40nuxt%2Fui' )
121+ expect ( ofetch ) . toHaveBeenNthCalledWith ( 1 , 'https://skilld.dev/api/skills/resolve' , {
122+ method : 'POST' ,
123+ body : { items : [ { packageName : '@nuxt/ui' , owner : undefined } ] } ,
124+ } )
125+ expect ( ofetch ) . toHaveBeenNthCalledWith ( 2 , 'https://skilld.dev/api/skills/nuxt/ui/@nuxt/ui' )
126+ } )
127+
128+ it ( 'maps detail payload into RegistrySkill' , async ( ) => {
129+ const { ofetch } = await import ( 'ofetch' )
130+ mockResolveAndDetail ( vi . mocked ( ofetch ) , {
131+ packageName : 'vue' ,
132+ owner : 'antfu' ,
133+ repo : 'skills' ,
134+ raw : '---\nname: vue\n---\n# vue' ,
135+ pushedAt : '2026-03-16T06:16:24Z' ,
136+ } )
137+
138+ const { fetchRegistrySkill } = await import ( '../../src/registry/client' )
139+ const skill = await fetchRegistrySkill ( 'vue' )
140+
141+ expect ( skill ) . toMatchObject ( {
142+ name : 'vue' ,
143+ packageName : 'vue' ,
144+ owner : 'antfu' ,
145+ repo : 'antfu/skills' ,
146+ content : '---\nname: vue\n---\n# vue' ,
147+ updatedAt : '2026-03-16T06:16:24Z' ,
148+ branch : 'main' ,
149+ } )
72150 } )
73151} )
0 commit comments