Commit 25489c2e authored by Ethan Reesor's avatar Ethan Reesor
Browse files

fix(remote fs): tell user when token is invalid

parent d60d2c19
Loading
Loading
Loading
Loading
+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';
@@ -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);
}

@@ -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 };
@@ -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];
      },
@@ -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;
      },
    }));
@@ -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;

+12 −0
Original line number Diff line number Diff line
@@ -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