Loading src/remotefs/gitlab_remote_file_system.test.ts +36 −10 Original line number Diff line number Diff line import * as vscode from 'vscode'; import { FetchError } from '../errors/fetch_error'; import { HelpError } from '../errors/help_error'; import { GitLabNewService } from '../gitlab/gitlab_new_service'; import { tokenService } from '../services/token_service'; import { GitLabRemoteFileSystem } from './gitlab_remote_file_system'; Loading @@ -16,15 +17,21 @@ interface ProjectInfo { files?: Record<string, RestRepositoryFile>; } function newFetchError(status: number, text: string) { function newFetchError(url: string | undefined, status: number, text: string, bodyJson?: unknown) { const response: Partial<Response> = { url: url as string, ok: false, redirected: false, status, statusText: text, trailer: Promise.reject(new Error('not implemented')), }; if (bodyJson) { response.json = () => Promise.resolve(bodyJson); } else { response.json = () => Promise.reject(new Error('no content')); } return new FetchError(text, response as Response); } Loading @@ -50,11 +57,11 @@ describe('GitLabRemoteFileSystem', () => { let instanceUrls: string[]; let projectInfo: ProjectInfo | null; const getProjectInfo = (id: number | string) => { const getProjectInfo = (url: string | undefined, id: number | string) => { if (projectInfo && (projectInfo.id === Number(id) || projectInfo.path === id)) { return projectInfo; } throw newFetchError(404, 'not found'); throw newFetchError(url, 404, 'not found'); }; const testProject = { id: 1 }; Loading Loading @@ -89,7 +96,7 @@ describe('GitLabRemoteFileSystem', () => { ref: string, projectId: number | string, ): Promise<RestRepositoryTreeEntry[]> { const proj = getProjectInfo(projectId); const proj = getProjectInfo(undefined, projectId); if (!proj.trees || !(path in proj.trees)) return []; return proj.trees[path]; }, Loading @@ -99,14 +106,14 @@ describe('GitLabRemoteFileSystem', () => { ref: string, projectId: number | string, ): Promise<RestRepositoryFile> { const proj = getProjectInfo(projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(404, 'not found'); const proj = getProjectInfo(undefined, projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(undefined, 404, 'not found'); return proj.files[path]; }, async getFileContent(path: string, ref: string, projectId: number | string): Promise<string> { const proj = getProjectInfo(projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(404, 'not found'); const proj = getProjectInfo(undefined, projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(undefined, 404, 'not found'); return proj.files[path].content; }, })); Loading Loading @@ -190,6 +197,25 @@ describe('GitLabRemoteFileSystem', () => { }); describe('stat', () => { it('throws a HelpError if the token is expired', async () => { const err = newFetchError('https://example.com', 401, 'unauthorized', { error: 'invalid_token', }); (GitLabNewService as jest.Mock).mockImplementation(() => ({ async getTree(): Promise<never> { throw err; }, async getFile(): Promise<never> { throw err; }, })); projectInfo = testProjectWithTree; const p = GitLabRemoteFileSystem.stat(testProjectFooURI); await expect(p).rejects.toThrowError(HelpError); }); it('returns directory info for a tree', async () => { projectInfo = testProjectWithTree; Loading src/remotefs/gitlab_remote_file_system.ts +12 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,18 @@ async function nullIf40x<T>(p: Promise<T>) { return await p; } catch (e) { if (e instanceof FetchError) { // Check if the response body is a GitLab invalid token error. Skip this // check if the URL is undefined. This avoids unnecessary complications // for testing. const body = await e.response.json().catch(() => undefined); if (body?.error === 'invalid_token' && e.response.url) { const { authority } = vscode.Uri.parse(e.response.url); throw new HelpError( `Failed to access a remote repository on ${authority} due to an expired or revoked access token. You must create a new token.`, { section: README_SECTIONS.SETUP }, ); } const s = e.response.status; // Let the handler deal with 40x responses Loading Loading
src/remotefs/gitlab_remote_file_system.test.ts +36 −10 Original line number Diff line number Diff line import * as vscode from 'vscode'; import { FetchError } from '../errors/fetch_error'; import { HelpError } from '../errors/help_error'; import { GitLabNewService } from '../gitlab/gitlab_new_service'; import { tokenService } from '../services/token_service'; import { GitLabRemoteFileSystem } from './gitlab_remote_file_system'; Loading @@ -16,15 +17,21 @@ interface ProjectInfo { files?: Record<string, RestRepositoryFile>; } function newFetchError(status: number, text: string) { function newFetchError(url: string | undefined, status: number, text: string, bodyJson?: unknown) { const response: Partial<Response> = { url: url as string, ok: false, redirected: false, status, statusText: text, trailer: Promise.reject(new Error('not implemented')), }; if (bodyJson) { response.json = () => Promise.resolve(bodyJson); } else { response.json = () => Promise.reject(new Error('no content')); } return new FetchError(text, response as Response); } Loading @@ -50,11 +57,11 @@ describe('GitLabRemoteFileSystem', () => { let instanceUrls: string[]; let projectInfo: ProjectInfo | null; const getProjectInfo = (id: number | string) => { const getProjectInfo = (url: string | undefined, id: number | string) => { if (projectInfo && (projectInfo.id === Number(id) || projectInfo.path === id)) { return projectInfo; } throw newFetchError(404, 'not found'); throw newFetchError(url, 404, 'not found'); }; const testProject = { id: 1 }; Loading Loading @@ -89,7 +96,7 @@ describe('GitLabRemoteFileSystem', () => { ref: string, projectId: number | string, ): Promise<RestRepositoryTreeEntry[]> { const proj = getProjectInfo(projectId); const proj = getProjectInfo(undefined, projectId); if (!proj.trees || !(path in proj.trees)) return []; return proj.trees[path]; }, Loading @@ -99,14 +106,14 @@ describe('GitLabRemoteFileSystem', () => { ref: string, projectId: number | string, ): Promise<RestRepositoryFile> { const proj = getProjectInfo(projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(404, 'not found'); const proj = getProjectInfo(undefined, projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(undefined, 404, 'not found'); return proj.files[path]; }, async getFileContent(path: string, ref: string, projectId: number | string): Promise<string> { const proj = getProjectInfo(projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(404, 'not found'); const proj = getProjectInfo(undefined, projectId); if (!proj.files || !(path in proj.files)) throw newFetchError(undefined, 404, 'not found'); return proj.files[path].content; }, })); Loading Loading @@ -190,6 +197,25 @@ describe('GitLabRemoteFileSystem', () => { }); describe('stat', () => { it('throws a HelpError if the token is expired', async () => { const err = newFetchError('https://example.com', 401, 'unauthorized', { error: 'invalid_token', }); (GitLabNewService as jest.Mock).mockImplementation(() => ({ async getTree(): Promise<never> { throw err; }, async getFile(): Promise<never> { throw err; }, })); projectInfo = testProjectWithTree; const p = GitLabRemoteFileSystem.stat(testProjectFooURI); await expect(p).rejects.toThrowError(HelpError); }); it('returns directory info for a tree', async () => { projectInfo = testProjectWithTree; Loading
src/remotefs/gitlab_remote_file_system.ts +12 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,18 @@ async function nullIf40x<T>(p: Promise<T>) { return await p; } catch (e) { if (e instanceof FetchError) { // Check if the response body is a GitLab invalid token error. Skip this // check if the URL is undefined. This avoids unnecessary complications // for testing. const body = await e.response.json().catch(() => undefined); if (body?.error === 'invalid_token' && e.response.url) { const { authority } = vscode.Uri.parse(e.response.url); throw new HelpError( `Failed to access a remote repository on ${authority} due to an expired or revoked access token. You must create a new token.`, { section: README_SECTIONS.SETUP }, ); } const s = e.response.status; // Let the handler deal with 40x responses Loading