@@ -10,6 +10,109 @@ afterEach(() => {
1010} )
1111
1212describe ( 'search command' , ( ) => {
13+ test ( '--token sends bearer auth and takes priority over SKILLHUB_TOKEN' , async ( ) => {
14+ let capturedAuth = ''
15+ const server = Bun . serve ( {
16+ port : 0 ,
17+ fetch ( req ) {
18+ const url = new URL ( req . url )
19+ if ( url . pathname === '/api/cli/v1/skills/search' ) {
20+ capturedAuth = req . headers . get ( 'authorization' ) ?? ''
21+ return Response . json ( {
22+ code : 0 ,
23+ data : {
24+ items : [ { namespace : 'global' , slug : 'pdf-parser' , latestVersion : '1.2.0' , summary : 'Parse PDFs' } ] ,
25+ total : 1 ,
26+ limit : 20
27+ }
28+ } )
29+ }
30+ return Response . json ( { code : 404 } , { status : 404 } )
31+ }
32+ } )
33+
34+ try {
35+ const result = await runCli (
36+ [ 'search' , 'pdf' , '--registry' , `http://localhost:${ server . port } ` , '--token' , 'sk_ok' ] ,
37+ { SKILLHUB_TOKEN : 'sk_bad' }
38+ )
39+
40+ expect ( result . exitCode ) . toBe ( 0 )
41+ expect ( capturedAuth ) . toBe ( 'Bearer sk_ok' )
42+ expect ( result . stdout ) . toContain ( 'global/pdf-parser' )
43+ } finally {
44+ server . stop ( )
45+ }
46+ } )
47+
48+ test ( 'bad --token fails with auth output and does not retry anonymously' , async ( ) => {
49+ const authHeaders : Array < string | null > = [ ]
50+ const server = Bun . serve ( {
51+ port : 0 ,
52+ fetch ( req ) {
53+ const url = new URL ( req . url )
54+ if ( url . pathname === '/api/cli/v1/skills/search' ) {
55+ const auth = req . headers . get ( 'authorization' )
56+ authHeaders . push ( auth )
57+ if ( auth === 'Bearer sk_bad' ) {
58+ return Response . json ( { code : 401 , message : 'unauthorized' } , { status : 401 } )
59+ }
60+ return Response . json ( {
61+ code : 0 ,
62+ data : {
63+ items : [ { namespace : 'global' , slug : 'anonymous-only' , latestVersion : '1.0.0' , summary : 'anonymous fallback' } ] ,
64+ total : 1 ,
65+ limit : 20
66+ }
67+ } )
68+ }
69+ return Response . json ( { code : 404 } , { status : 404 } )
70+ }
71+ } )
72+
73+ try {
74+ const registryUrl = `http://localhost:${ server . port } `
75+ const result = await runCli ( [ 'search' , 'pdf' , '--registry' , registryUrl , '--token' , 'sk_bad' ] )
76+
77+ expect ( result . exitCode ) . toBe ( 2 )
78+ expect ( result . stderr ) . toContain ( 'Error: authentication failed' )
79+ expect ( result . stderr ) . toContain ( `Context: registry ${ registryUrl } ` )
80+ expect ( result . stderr ) . toContain ( 'Next:' )
81+ expect ( authHeaders ) . toEqual ( [ 'Bearer sk_bad' ] )
82+ } finally {
83+ server . stop ( )
84+ }
85+ } )
86+
87+ test ( 'bad --token returns structured json auth error' , async ( ) => {
88+ const server = Bun . serve ( {
89+ port : 0 ,
90+ fetch ( req ) {
91+ const url = new URL ( req . url )
92+ if ( url . pathname === '/api/cli/v1/skills/search' ) {
93+ return Response . json ( { code : 401 , message : 'unauthorized' } , { status : 401 } )
94+ }
95+ return Response . json ( { code : 404 } , { status : 404 } )
96+ }
97+ } )
98+
99+ try {
100+ const registryUrl = `http://localhost:${ server . port } `
101+ const result = await runCli ( [ 'search' , 'pdf' , '--registry' , registryUrl , '--token' , 'sk_bad' , '--json' ] )
102+
103+ expect ( result . exitCode ) . toBe ( 2 )
104+ const parsed = JSON . parse ( result . stderr )
105+ expect ( parsed . ok ) . toBe ( false )
106+ expect ( parsed . message ) . toBe ( 'authentication failed' )
107+ expect ( parsed . exitCode ) . toBe ( 2 )
108+ expect ( parsed . details . registry ) . toBe ( registryUrl )
109+ expect ( typeof parsed . details . next ) . toBe ( 'string' )
110+ expect ( parsed . details . next ) . toContain ( 'skillhub login' )
111+ } finally {
112+ server . stop ( )
113+ }
114+ } )
115+
13116 test ( 'prints compact search table' , async ( ) => {
14117 registry = await startFakeRegistry ( {
15118 searchItems : [ { namespace : 'global' , slug : 'pdf-parser' , latestVersion : '1.2.0' , summary : 'Parse PDFs' } ]
0 commit comments