@@ -11,8 +11,10 @@ import {
1111 hashSkillZip ,
1212 listTextFiles ,
1313 readLockfile ,
14+ readSkillOrigin ,
1415 sha256Hex ,
1516 writeLockfile ,
17+ writeSkillOrigin ,
1618} from './skills'
1719
1820describe ( 'skills' , ( ) => {
@@ -46,6 +48,18 @@ describe('skills', () => {
4648 expect ( read ) . toEqual ( { version : 1 , skills : { } } )
4749 } )
4850
51+ it ( 'returns empty lockfile on schema mismatch' , async ( ) => {
52+ const workdir = await mkdtemp ( join ( tmpdir ( ) , 'clawdhub-work-schema-' ) )
53+ await mkdir ( join ( workdir , '.clawdhub' ) , { recursive : true } )
54+ await writeFile (
55+ join ( workdir , '.clawdhub' , 'lock.json' ) ,
56+ JSON . stringify ( { version : 1 , skills : 'nope' } ) ,
57+ 'utf8' ,
58+ )
59+ const read = await readLockfile ( workdir )
60+ expect ( read ) . toEqual ( { version : 1 , skills : { } } )
61+ } )
62+
4963 it ( 'skips dotfiles and node_modules when listing text files' , async ( ) => {
5064 const workdir = await mkdtemp ( join ( tmpdir ( ) , 'clawdhub-files-' ) )
5165 await writeFile ( join ( workdir , 'SKILL.md' ) , 'hi' , 'utf8' )
@@ -74,6 +88,16 @@ describe('skills', () => {
7488 )
7589 } )
7690
91+ it ( 'falls back to text/plain for unknown text extensions' , async ( ) => {
92+ const workdir = await mkdtemp ( join ( tmpdir ( ) , 'clawdhub-env-' ) )
93+ await writeFile ( join ( workdir , 'SKILL.md' ) , 'hi' , 'utf8' )
94+ await writeFile ( join ( workdir , 'config.env' ) , 'TOKEN=demo' , 'utf8' )
95+ const files = await listTextFiles ( workdir )
96+ expect ( files . find ( ( file ) => file . relPath === 'config.env' ) ?. contentType ) . toBe (
97+ 'text/plain' ,
98+ )
99+ } )
100+
77101 it ( 'hashes skill files deterministically' , async ( ) => {
78102 const { fingerprint } = hashSkillFiles ( [
79103 { relPath : 'b.txt' , bytes : strToU8 ( 'b' ) } ,
@@ -99,4 +123,72 @@ describe('skills', () => {
99123 ] )
100124 expect ( fingerprint ) . toBe ( expected )
101125 } )
126+
127+ it ( 'ignores unsafe or non-text entries when hashing zips' , ( ) => {
128+ const zip = zipSync ( {
129+ 'SKILL.md' : strToU8 ( 'hello' ) ,
130+ 'folder/' : strToU8 ( '' ) ,
131+ '../evil.txt' : strToU8 ( 'nope' ) ,
132+ 'bad\\path.txt' : strToU8 ( 'nope' ) ,
133+ 'image.png' : strToU8 ( 'nope' ) ,
134+ } )
135+ const { files } = hashSkillZip ( new Uint8Array ( zip ) )
136+ expect ( files ) . toEqual ( [
137+ { path : 'SKILL.md' , sha256 : sha256Hex ( strToU8 ( 'hello' ) ) , size : 5 } ,
138+ ] )
139+ } )
140+
141+ it ( 'builds fingerprints from valid entries only' , ( ) => {
142+ const fingerprint = buildSkillFingerprint ( [
143+ { path : '' , sha256 : '' } ,
144+ { path : 'valid.txt' , sha256 : sha256Hex ( strToU8 ( 'ok' ) ) } ,
145+ ] )
146+ const expected = buildSkillFingerprint ( [
147+ { path : 'valid.txt' , sha256 : sha256Hex ( strToU8 ( 'ok' ) ) } ,
148+ ] )
149+ expect ( fingerprint ) . toBe ( expected )
150+ } )
151+
152+ it ( 'returns null for invalid skill origin metadata' , async ( ) => {
153+ const workdir = await mkdtemp ( join ( tmpdir ( ) , 'clawdhub-origin-' ) )
154+ expect ( await readSkillOrigin ( workdir ) ) . toBeNull ( )
155+
156+ await mkdir ( join ( workdir , '.clawdhub' ) , { recursive : true } )
157+ await writeFile (
158+ join ( workdir , '.clawdhub' , 'origin.json' ) ,
159+ JSON . stringify ( { version : 2 } ) ,
160+ 'utf8' ,
161+ )
162+ expect ( await readSkillOrigin ( workdir ) ) . toBeNull ( )
163+
164+ await writeFile (
165+ join ( workdir , '.clawdhub' , 'origin.json' ) ,
166+ JSON . stringify ( { version : 1 , registry : 'demo' , slug : 'x' , installedAt : 1 } ) ,
167+ 'utf8' ,
168+ )
169+ expect ( await readSkillOrigin ( workdir ) ) . toBeNull ( )
170+
171+ await writeFile (
172+ join ( workdir , '.clawdhub' , 'origin.json' ) ,
173+ JSON . stringify ( {
174+ version : 1 ,
175+ registry : 'demo' ,
176+ slug : 'x' ,
177+ installedVersion : '0.1.0' ,
178+ installedAt : 'nope' ,
179+ } ) ,
180+ 'utf8' ,
181+ )
182+ expect ( await readSkillOrigin ( workdir ) ) . toBeNull ( )
183+
184+ const origin = {
185+ version : 1 ,
186+ registry : 'https://example.com' ,
187+ slug : 'demo' ,
188+ installedVersion : '1.2.3' ,
189+ installedAt : 123 ,
190+ }
191+ await writeSkillOrigin ( workdir , origin )
192+ expect ( await readSkillOrigin ( workdir ) ) . toEqual ( origin )
193+ } )
102194} )
0 commit comments