@@ -65,12 +65,17 @@ describe('TokenDialog component', () => {
6565 const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
6666 const instance : any = wrapper . instance ( ) ;
6767
68- wrapper . setState ( { verifying : true , tokenInput : 'hello' } ) ;
68+ wrapper . setState ( {
69+ verifying : true ,
70+ tokenInput : 'hello' ,
71+ errorMessage : 'test error' ,
72+ } ) ;
6973 instance . reset ( ) ;
7074
7175 expect ( wrapper . state ( ) ) . toEqual ( {
7276 verifying : false ,
7377 error : false ,
78+ errorMessage : undefined ,
7479 tokenInput : '' ,
7580 } ) ;
7681 } ) ;
@@ -79,12 +84,17 @@ describe('TokenDialog component', () => {
7984 const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
8085 const instance : any = wrapper . instance ( ) ;
8186
82- wrapper . setState ( { verifying : true , tokenInput : 'hello' } ) ;
87+ wrapper . setState ( {
88+ verifying : true ,
89+ tokenInput : 'hello' ,
90+ errorMessage : 'test error' ,
91+ } ) ;
8392 instance . onClose ( ) ;
8493
8594 expect ( wrapper . state ( ) ) . toEqual ( {
8695 verifying : false ,
8796 error : false ,
97+ errorMessage : undefined ,
8898 tokenInput : '' ,
8999 } ) ;
90100 } ) ;
@@ -121,7 +131,12 @@ describe('TokenDialog component', () => {
121131 mockOctokit = {
122132 authenticate : vi . fn ( ) ,
123133 users : {
124- getAuthenticated : vi . fn ( ) . mockResolvedValue ( { data : mockUser } ) ,
134+ getAuthenticated : vi . fn ( ) . mockResolvedValue ( {
135+ data : mockUser ,
136+ headers : {
137+ 'x-oauth-scopes' : 'gist, repo' ,
138+ } ,
139+ } ) ,
125140 } ,
126141 } as unknown as Octokit ;
127142
@@ -149,11 +164,55 @@ describe('TokenDialog component', () => {
149164 expect ( store . gitHubLogin ) . toBe ( mockUser . login ) ;
150165 expect ( store . gitHubName ) . toBe ( mockUser . name ) ;
151166 expect ( store . gitHubAvatarUrl ) . toBe ( mockUser . avatar_url ) ;
167+ expect ( wrapper . state ( 'error' ) ) . toBe ( false ) ;
168+ expect ( store . isTokenDialogShowing ) . toBe ( false ) ;
169+ } ) ;
170+
171+ it ( 'handles an invalid token error' , async ( ) => {
172+ vi . mocked ( mockOctokit . users . getAuthenticated ) . mockRejectedValue (
173+ new Error ( 'Bad credentials' ) ,
174+ ) ;
175+
176+ const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
177+ wrapper . setState ( { tokenInput : mockValidToken } ) ;
178+ const instance : any = wrapper . instance ( ) ;
179+
180+ await instance . onSubmitToken ( ) ;
181+
182+ expect ( wrapper . state ( 'error' ) ) . toBe ( true ) ;
183+ expect ( wrapper . state ( 'errorMessage' ) ) . toBe (
184+ 'Invalid GitHub token. Please check your token and try again.' ,
185+ ) ;
186+ expect ( store . gitHubToken ) . toEqual ( null ) ;
187+ } ) ;
188+
189+ it ( 'handles missing gist scope' , async ( ) => {
190+ vi . mocked ( mockOctokit . users . getAuthenticated ) . mockResolvedValue ( {
191+ data : mockUser ,
192+ headers : {
193+ 'x-oauth-scopes' : 'repo, user' , // Missing 'gist' scope
194+ } ,
195+ } as any ) ;
196+
197+ const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
198+ wrapper . setState ( { tokenInput : mockValidToken } ) ;
199+ const instance : any = wrapper . instance ( ) ;
200+
201+ await instance . onSubmitToken ( ) ;
202+
203+ expect ( wrapper . state ( 'error' ) ) . toBe ( true ) ;
204+ expect ( wrapper . state ( 'errorMessage' ) ) . toBe (
205+ 'Token is missing the "gist" scope. Please generate a new token with gist permissions.' ,
206+ ) ;
207+ expect ( store . gitHubToken ) . toEqual ( null ) ;
152208 } ) ;
153209
154- it ( 'handles an error ' , async ( ) => {
210+ it ( 'handles empty scopes header ' , async ( ) => {
155211 vi . mocked ( mockOctokit . users . getAuthenticated ) . mockResolvedValue ( {
156- data : null ,
212+ data : mockUser ,
213+ headers : {
214+ // No x-oauth-scopes header
215+ } ,
157216 } as any ) ;
158217
159218 const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
@@ -163,7 +222,110 @@ describe('TokenDialog component', () => {
163222 await instance . onSubmitToken ( ) ;
164223
165224 expect ( wrapper . state ( 'error' ) ) . toBe ( true ) ;
225+ expect ( wrapper . state ( 'errorMessage' ) ) . toBe (
226+ 'Token is missing the "gist" scope. Please generate a new token with gist permissions.' ,
227+ ) ;
166228 expect ( store . gitHubToken ) . toEqual ( null ) ;
167229 } ) ;
168230 } ) ;
231+
232+ describe ( 'validateGitHubToken()' , ( ) => {
233+ let mockOctokit : Octokit ;
234+ const mockUser = {
235+ avatar_url : 'https://avatars.fake/hi' ,
236+ login : 'test-login' ,
237+ name : 'Test User' ,
238+ } as const ;
239+
240+ beforeEach ( ( ) => {
241+ mockOctokit = {
242+ users : {
243+ getAuthenticated : vi . fn ( ) ,
244+ } ,
245+ } as unknown as Octokit ;
246+
247+ vi . mocked ( getOctokit ) . mockResolvedValue ( mockOctokit ) ;
248+ } ) ;
249+
250+ it ( 'validates a token with gist scope' , async ( ) => {
251+ vi . mocked ( mockOctokit . users . getAuthenticated ) . mockResolvedValue ( {
252+ data : mockUser ,
253+ headers : {
254+ 'x-oauth-scopes' : 'gist, repo, user' ,
255+ } ,
256+ } as any ) ;
257+
258+ const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
259+ const instance : any = wrapper . instance ( ) ;
260+
261+ const result = await instance . validateGitHubToken ( 'valid-token' ) ;
262+
263+ expect ( result ) . toEqual ( {
264+ isValid : true ,
265+ scopes : [ 'gist' , 'repo' , 'user' ] ,
266+ hasGistScope : true ,
267+ user : mockUser ,
268+ } ) ;
269+ } ) ;
270+
271+ it ( 'validates a token without gist scope' , async ( ) => {
272+ vi . mocked ( mockOctokit . users . getAuthenticated ) . mockResolvedValue ( {
273+ data : mockUser ,
274+ headers : {
275+ 'x-oauth-scopes' : 'repo, user' ,
276+ } ,
277+ } as any ) ;
278+
279+ const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
280+ const instance : any = wrapper . instance ( ) ;
281+
282+ const result = await instance . validateGitHubToken ( 'token-without-gist' ) ;
283+
284+ expect ( result ) . toEqual ( {
285+ isValid : true ,
286+ scopes : [ 'repo' , 'user' ] ,
287+ hasGistScope : false ,
288+ user : mockUser ,
289+ } ) ;
290+ } ) ;
291+
292+ it ( 'handles invalid token' , async ( ) => {
293+ vi . mocked ( mockOctokit . users . getAuthenticated ) . mockRejectedValue (
294+ new Error ( 'Bad credentials' ) ,
295+ ) ;
296+
297+ const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
298+ const instance : any = wrapper . instance ( ) ;
299+
300+ const result = await instance . validateGitHubToken ( 'invalid-token' ) ;
301+
302+ expect ( result ) . toEqual ( {
303+ isValid : false ,
304+ scopes : [ ] ,
305+ hasGistScope : false ,
306+ error : 'Bad credentials' ,
307+ } ) ;
308+ } ) ;
309+
310+ it ( 'handles missing scopes header' , async ( ) => {
311+ vi . mocked ( mockOctokit . users . getAuthenticated ) . mockResolvedValue ( {
312+ data : mockUser ,
313+ headers : { } ,
314+ } as any ) ;
315+
316+ const wrapper = shallow ( < TokenDialog appState = { store } /> ) ;
317+ const instance : any = wrapper . instance ( ) ;
318+
319+ const result = await instance . validateGitHubToken (
320+ 'token-no-scopes-header' ,
321+ ) ;
322+
323+ expect ( result ) . toEqual ( {
324+ isValid : true ,
325+ scopes : [ ] ,
326+ hasGistScope : false ,
327+ user : mockUser ,
328+ } ) ;
329+ } ) ;
330+ } ) ;
169331} ) ;
0 commit comments