import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
import httpStatus from '~/lib/utils/http_status';

describe('Api', () => {
  const dummyApiVersion = 'v3000';
  const dummyUrlRoot = '/gitlab';
  const dummyGon = {
    api_version: dummyApiVersion,
    relative_url_root: dummyUrlRoot,
  };
  let originalGon;
  let mock;

  beforeEach(() => {
    mock = new MockAdapter(axios);
    originalGon = window.gon;
    window.gon = { ...dummyGon };
  });

  afterEach(() => {
    mock.restore();
    window.gon = originalGon;
  });

  describe('buildUrl', () => {
    it('adds URL root and fills in API version', () => {
      const input = '/api/:version/foo/bar';
      const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`;

      const builtUrl = Api.buildUrl(input);

      expect(builtUrl).toEqual(expectedOutput);
    });

    [null, '', '/'].forEach(root => {
      it(`works when relative_url_root is ${root}`, () => {
        window.gon.relative_url_root = root;
        const input = '/api/:version/foo/bar';
        const expectedOutput = `/api/${dummyApiVersion}/foo/bar`;

        const builtUrl = Api.buildUrl(input);

        expect(builtUrl).toEqual(expectedOutput);
      });
    });
  });

  describe('packages', () => {
    const projectId = 'project_a';
    const packageId = 'package_b';
    const apiResponse = [{ id: 1, name: 'foo' }];

    describe('groupPackages', () => {
      const groupId = 'group_a';

      it('fetch all group packages', () => {
        const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/packages`;
        jest.spyOn(axios, 'get');
        mock.onGet(expectedUrl).replyOnce(httpStatus.OK, apiResponse);

        return Api.groupPackages(groupId).then(({ data }) => {
          expect(data).toEqual(apiResponse);
          expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
        });
      });
    });

    describe('projectPackages', () => {
      it('fetch all project packages', () => {
        const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/packages`;
        jest.spyOn(axios, 'get');
        mock.onGet(expectedUrl).replyOnce(httpStatus.OK, apiResponse);

        return Api.projectPackages(projectId).then(({ data }) => {
          expect(data).toEqual(apiResponse);
          expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
        });
      });
    });

    describe('buildProjectPackageUrl', () => {
      it('returns the right url', () => {
        const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/packages/${packageId}`;
        const url = Api.buildProjectPackageUrl(projectId, packageId);
        expect(url).toEqual(expectedUrl);
      });
    });

    describe('projectPackage', () => {
      it('fetch package details', () => {
        const expectedUrl = `foo`;
        jest.spyOn(Api, 'buildProjectPackageUrl').mockReturnValue(expectedUrl);
        jest.spyOn(axios, 'get');
        mock.onGet(expectedUrl).replyOnce(httpStatus.OK, apiResponse);

        return Api.projectPackage(projectId, packageId).then(({ data }) => {
          expect(data).toEqual(apiResponse);
          expect(axios.get).toHaveBeenCalledWith(expectedUrl);
        });
      });
    });

    describe('deleteProjectPackage', () => {
      it('delete a package', () => {
        const expectedUrl = `foo`;

        jest.spyOn(Api, 'buildProjectPackageUrl').mockReturnValue(expectedUrl);
        jest.spyOn(axios, 'delete');
        mock.onDelete(expectedUrl).replyOnce(httpStatus.OK, true);

        return Api.deleteProjectPackage(projectId, packageId).then(({ data }) => {
          expect(data).toEqual(true);
          expect(axios.delete).toHaveBeenCalledWith(expectedUrl);
        });
      });
    });
  });

  describe('container registry', () => {
    describe('containerRegistryDetails', () => {
      it('fetch container registry  details', async () => {
        const expectedUrl = `foo`;
        const apiResponse = {};

        jest.spyOn(axios, 'get');
        jest.spyOn(Api, 'buildUrl').mockReturnValueOnce(expectedUrl);
        mock.onGet(expectedUrl).replyOnce(httpStatus.OK, apiResponse);

        const { data } = await Api.containerRegistryDetails(1);

        expect(data).toEqual(apiResponse);
        expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
      });
    });
  });

  describe('group', () => {
    it('fetches a group', done => {
      const groupId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, {
        name: 'test',
      });

      Api.group(groupId, response => {
        expect(response.name).toBe('test');
        done();
      });
    });
  });

  describe('groupMembers', () => {
    it('fetches group members', done => {
      const groupId = '54321';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`;
      const expectedData = [{ id: 7 }];
      mock.onGet(expectedUrl).reply(httpStatus.OK, expectedData);

      Api.groupMembers(groupId)
        .then(({ data }) => {
          expect(data).toEqual(expectedData);
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('groupMilestones', () => {
    it('fetches group milestones', done => {
      const groupId = '16';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/milestones`;
      const expectedData = [
        {
          id: 12,
          iid: 3,
          group_id: 16,
          title: '10.0',
          description: 'Version',
          due_date: '2013-11-29',
          start_date: '2013-11-10',
          state: 'active',
          updated_at: '2013-10-02T09:24:18Z',
          created_at: '2013-10-02T09:24:18Z',
          web_url: 'https://gitlab.com/groups/gitlab-org/-/milestones/42',
        },
      ];
      mock.onGet(expectedUrl).reply(httpStatus.OK, expectedData);

      Api.groupMilestones(groupId)
        .then(({ data }) => {
          expect(data).toEqual(expectedData);
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('groups', () => {
    it('fetches groups', done => {
      const query = 'dummy query';
      const options = { unused: 'option' };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.groups(query, options, response => {
        expect(response.length).toBe(1);
        expect(response[0].name).toBe('test');
        done();
      });
    });
  });

  describe('namespaces', () => {
    it('fetches namespaces', done => {
      const query = 'dummy query';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.namespaces(query, response => {
        expect(response.length).toBe(1);
        expect(response[0].name).toBe('test');
        done();
      });
    });
  });

  describe('projects', () => {
    it('fetches projects with membership when logged in', done => {
      const query = 'dummy query';
      const options = { unused: 'option' };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
      window.gon.current_user_id = 1;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.projects(query, options, response => {
        expect(response.length).toBe(1);
        expect(response[0].name).toBe('test');
        done();
      });
    });

    it('fetches projects without membership when not logged in', done => {
      const query = 'dummy query';
      const options = { unused: 'option' };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.projects(query, options, response => {
        expect(response.length).toBe(1);
        expect(response[0].name).toBe('test');
        done();
      });
    });
  });

  describe('updateProject', () => {
    it('update a project with the given payload', done => {
      const projectPath = 'foo';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}`;
      mock.onPut(expectedUrl).reply(httpStatus.OK, { foo: 'bar' });

      Api.updateProject(projectPath, { foo: 'bar' })
        .then(({ data }) => {
          expect(data.foo).toBe('bar');
          done();
        })
        .catch(done.fail);
    });
  });

  describe('projectUsers', () => {
    it('fetches all users of a particular project', done => {
      const query = 'dummy query';
      const options = { unused: 'option' };
      const projectPath = 'gitlab-org%2Fgitlab-ce';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/users`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.projectUsers('gitlab-org/gitlab-ce', query, options)
        .then(response => {
          expect(response.length).toBe(1);
          expect(response[0].name).toBe('test');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('projectMergeRequests', () => {
    const projectPath = 'abc';
    const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`;

    it('fetches all merge requests for a project', done => {
      const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }];
      mock.onGet(expectedUrl).reply(httpStatus.OK, mockData);
      Api.projectMergeRequests(projectPath)
        .then(({ data }) => {
          expect(data.length).toEqual(2);
          expect(data[0].source_branch).toBe('foo');
          expect(data[1].source_branch).toBe('bar');
        })
        .then(done)
        .catch(done.fail);
    });

    it('fetches merge requests filtered with passed params', done => {
      const params = {
        source_branch: 'bar',
      };
      const mockData = [{ source_branch: 'bar' }];
      mock.onGet(expectedUrl, { params }).reply(httpStatus.OK, mockData);

      Api.projectMergeRequests(projectPath, params)
        .then(({ data }) => {
          expect(data.length).toEqual(1);
          expect(data[0].source_branch).toBe('bar');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('projectMergeRequest', () => {
    it('fetches a merge request', done => {
      const projectPath = 'abc';
      const mergeRequestId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, {
        title: 'test',
      });

      Api.projectMergeRequest(projectPath, mergeRequestId)
        .then(({ data }) => {
          expect(data.title).toBe('test');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('projectMergeRequestChanges', () => {
    it('fetches the changes of a merge request', done => {
      const projectPath = 'abc';
      const mergeRequestId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/changes`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, {
        title: 'test',
      });

      Api.projectMergeRequestChanges(projectPath, mergeRequestId)
        .then(({ data }) => {
          expect(data.title).toBe('test');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('projectMergeRequestVersions', () => {
    it('fetches the versions of a merge request', done => {
      const projectPath = 'abc';
      const mergeRequestId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/versions`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          id: 123,
        },
      ]);

      Api.projectMergeRequestVersions(projectPath, mergeRequestId)
        .then(({ data }) => {
          expect(data.length).toBe(1);
          expect(data[0].id).toBe(123);
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('projectRunners', () => {
    it('fetches the runners of a project', done => {
      const projectPath = 7;
      const params = { scope: 'active' };
      const mockData = [{ id: 4 }];
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`;
      mock.onGet(expectedUrl, { params }).reply(httpStatus.OK, mockData);

      Api.projectRunners(projectPath, { params })
        .then(({ data }) => {
          expect(data).toEqual(mockData);
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('projectMilestones', () => {
    it('fetches project milestones', done => {
      const projectId = 1;
      const options = { state: 'active' };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/1/milestones`;
      mock.onGet(expectedUrl).reply(200, [
        {
          id: 1,
          title: 'milestone1',
          state: 'active',
        },
      ]);

      Api.projectMilestones(projectId, options)
        .then(({ data }) => {
          expect(data.length).toBe(1);
          expect(data[0].title).toBe('milestone1');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('addProjectIssueAsTodo', () => {
    it('adds issue ID as a todo', () => {
      const projectId = 1;
      const issueIid = 11;
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/1/issues/11/todo`;
      mock.onPost(expectedUrl).reply(200, {
        id: 112,
        project: {
          id: 1,
        },
      });

      return Api.addProjectIssueAsTodo(projectId, issueIid).then(({ data }) => {
        expect(data.id).toBe(112);
        expect(data.project.id).toBe(projectId);
      });
    });
  });

  describe('newLabel', () => {
    it('creates a new label', done => {
      const namespace = 'some namespace';
      const project = 'some project';
      const labelData = { some: 'data' };
      const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/-/labels`;
      const expectedData = {
        label: labelData,
      };
      mock.onPost(expectedUrl).reply(config => {
        expect(config.data).toBe(JSON.stringify(expectedData));

        return [
          httpStatus.OK,
          {
            name: 'test',
          },
        ];
      });

      Api.newLabel(namespace, project, labelData, response => {
        expect(response.name).toBe('test');
        done();
      });
    });

    it('creates a group label', done => {
      const namespace = 'group/subgroup';
      const labelData = { some: 'data' };
      const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace);
      const expectedData = {
        label: labelData,
      };
      mock.onPost(expectedUrl).reply(config => {
        expect(config.data).toBe(JSON.stringify(expectedData));

        return [
          httpStatus.OK,
          {
            name: 'test',
          },
        ];
      });

      Api.newLabel(namespace, undefined, labelData, response => {
        expect(response.name).toBe('test');
        done();
      });
    });
  });

  describe('groupProjects', () => {
    it('fetches group projects', done => {
      const groupId = '123456';
      const query = 'dummy query';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.groupProjects(groupId, query, {}, response => {
        expect(response.length).toBe(1);
        expect(response[0].name).toBe('test');
        done();
      });
    });
  });

  describe('commit', () => {
    const projectId = 'user/project';
    const sha = 'abcd0123';
    const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
      projectId,
    )}/repository/commits/${sha}`;

    it('fetches a single commit', () => {
      mock.onGet(expectedUrl).reply(httpStatus.OK, { id: sha });

      return Api.commit(projectId, sha).then(({ data: commit }) => {
        expect(commit.id).toBe(sha);
      });
    });

    it('fetches a single commit without stats', () => {
      mock.onGet(expectedUrl, { params: { stats: false } }).reply(httpStatus.OK, { id: sha });

      return Api.commit(projectId, sha, { stats: false }).then(({ data: commit }) => {
        expect(commit.id).toBe(sha);
      });
    });
  });

  describe('issueTemplate', () => {
    const namespace = 'some namespace';
    const project = 'some project';
    const templateKey = ' template #%?.key ';
    const templateType = 'template type';
    const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(
      templateKey,
    )}`;

    it('fetches an issue template', done => {
      mock.onGet(expectedUrl).reply(httpStatus.OK, 'test');

      Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
        expect(response).toBe('test');
        done();
      });
    });

    describe('when an error occurs while fetching an issue template', () => {
      it('rejects the Promise', () => {
        mock.onGet(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

        Api.issueTemplate(namespace, project, templateKey, templateType, () => {
          expect(mock.history.get).toHaveLength(1);
        });
      });
    });
  });

  describe('issueTemplates', () => {
    const namespace = 'some namespace';
    const project = 'some project';
    const templateType = 'template type';
    const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}`;

    it('fetches all templates by type', done => {
      const expectedData = [
        { key: 'Template1', name: 'Template 1', content: 'This is template 1!' },
      ];
      mock.onGet(expectedUrl).reply(httpStatus.OK, expectedData);

      Api.issueTemplates(namespace, project, templateType, (error, response) => {
        expect(response.length).toBe(1);
        const { key, name, content } = response[0];
        expect(key).toBe('Template1');
        expect(name).toBe('Template 1');
        expect(content).toBe('This is template 1!');
        done();
      });
    });

    describe('when an error occurs while fetching issue templates', () => {
      it('rejects the Promise', () => {
        mock.onGet(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

        Api.issueTemplates(namespace, project, templateType, () => {
          expect(mock.history.get).toHaveLength(1);
        });
      });
    });
  });

  describe('projectTemplates', () => {
    it('fetches a list of templates', done => {
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`;

      mock.onGet(expectedUrl).reply(httpStatus.OK, 'test');

      Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => {
        expect(response).toBe('test');
        done();
      });
    });
  });

  describe('projectTemplate', () => {
    it('fetches a single template', done => {
      const data = { unused: 'option' };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`;

      mock.onGet(expectedUrl).reply(httpStatus.OK, 'test');

      Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => {
        expect(response).toBe('test');
        done();
      });
    });
  });

  describe('users', () => {
    it('fetches users', done => {
      const query = 'dummy query';
      const options = { unused: 'option' };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.users(query, options)
        .then(({ data }) => {
          expect(data.length).toBe(1);
          expect(data[0].name).toBe('test');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('user', () => {
    it('fetches single user', done => {
      const userId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, {
        name: 'testuser',
      });

      Api.user(userId)
        .then(({ data }) => {
          expect(data.name).toBe('testuser');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('user counts', () => {
    it('fetches single user counts', done => {
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/user_counts`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, {
        merge_requests: 4,
      });

      Api.userCounts()
        .then(({ data }) => {
          expect(data.merge_requests).toBe(4);
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('user status', () => {
    it('fetches single user status', done => {
      const userId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, {
        message: 'testmessage',
      });

      Api.userStatus(userId)
        .then(({ data }) => {
          expect(data.message).toBe('testmessage');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('user projects', () => {
    it('fetches all projects that belong to a particular user', done => {
      const query = 'dummy query';
      const options = { unused: 'option' };
      const userId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/projects`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.userProjects(userId, query, options, response => {
        expect(response.length).toBe(1);
        expect(response[0].name).toBe('test');
        done();
      });
    });
  });

  describe('commitPipelines', () => {
    it('fetches pipelines for a given commit', done => {
      const projectId = 'example/foobar';
      const commitSha = 'abc123def';
      const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.commitPipelines(projectId, commitSha)
        .then(({ data }) => {
          expect(data.length).toBe(1);
          expect(data[0].name).toBe('test');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('pipelineJobs', () => {
    it.each([undefined, {}, { foo: true }])(
      'fetches the jobs for a given pipeline given %p params',
      async params => {
        const projectId = 123;
        const pipelineId = 456;
        const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/pipelines/${pipelineId}/jobs`;
        const payload = [
          {
            name: 'test',
          },
        ];
        mock.onGet(expectedUrl, { params }).reply(httpStatus.OK, payload);

        const { data } = await Api.pipelineJobs(projectId, pipelineId, params);
        expect(data).toEqual(payload);
      },
    );
  });

  describe('createBranch', () => {
    it('creates new branch', done => {
      const ref = 'master';
      const branch = 'new-branch-name';
      const dummyProjectPath = 'gitlab-org/gitlab-ce';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
        dummyProjectPath,
      )}/repository/branches`;

      jest.spyOn(axios, 'post');

      mock.onPost(expectedUrl).replyOnce(httpStatus.OK, {
        name: branch,
      });

      Api.createBranch(dummyProjectPath, { ref, branch })
        .then(({ data }) => {
          expect(data.name).toBe(branch);
          expect(axios.post).toHaveBeenCalledWith(expectedUrl, { ref, branch });
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('projectForks', () => {
    it('gets forked projects', done => {
      const dummyProjectPath = 'gitlab-org/gitlab-ce';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
        dummyProjectPath,
      )}/forks`;

      jest.spyOn(axios, 'get');

      mock.onGet(expectedUrl).replyOnce(httpStatus.OK, ['fork']);

      Api.projectForks(dummyProjectPath, { visibility: 'private' })
        .then(({ data }) => {
          expect(data).toEqual(['fork']);
          expect(axios.get).toHaveBeenCalledWith(expectedUrl, {
            params: { visibility: 'private' },
          });
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('createContextCommits', () => {
    it('creates a new context commit', done => {
      const projectPath = 'abc';
      const mergeRequestId = '123456';
      const commitsData = ['abcdefg'];
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/context_commits`;
      const expectedData = {
        commits: commitsData,
      };

      jest.spyOn(axios, 'post');

      mock.onPost(expectedUrl).replyOnce(200, [
        {
          id: 'abcdefghijklmnop',
          short_id: 'abcdefg',
          title: 'Dummy commit',
        },
      ]);

      Api.createContextCommits(projectPath, mergeRequestId, expectedData)
        .then(({ data }) => {
          expect(data[0].title).toBe('Dummy commit');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('allContextCommits', () => {
    it('gets all context commits', done => {
      const projectPath = 'abc';
      const mergeRequestId = '123456';
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/context_commits`;

      jest.spyOn(axios, 'get');

      mock
        .onGet(expectedUrl)
        .replyOnce(200, [{ id: 'abcdef', short_id: 'abcdefghi', title: 'Dummy commit title' }]);

      Api.allContextCommits(projectPath, mergeRequestId)
        .then(({ data }) => {
          expect(data[0].title).toBe('Dummy commit title');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('removeContextCommits', () => {
    it('removes context commits', done => {
      const projectPath = 'abc';
      const mergeRequestId = '123456';
      const commitsData = ['abcdefg'];
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/context_commits`;
      const expectedData = {
        commits: commitsData,
      };

      jest.spyOn(axios, 'delete');

      mock.onDelete(expectedUrl).replyOnce(204);

      Api.removeContextCommits(projectPath, mergeRequestId, expectedData)
        .then(() => {
          expect(axios.delete).toHaveBeenCalledWith(expectedUrl, { data: expectedData });
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('release-related methods', () => {
    const dummyProjectPath = 'gitlab-org/gitlab';
    const dummyTagName = 'v1.3';
    const baseReleaseUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
      dummyProjectPath,
    )}/releases`;

    describe('releases', () => {
      const expectedUrl = baseReleaseUrl;

      describe('when releases are successfully returned', () => {
        it('resolves the Promise', () => {
          mock.onGet(expectedUrl).replyOnce(httpStatus.OK);

          return Api.releases(dummyProjectPath).then(() => {
            expect(mock.history.get).toHaveLength(1);
          });
        });
      });

      describe('when an error occurs while fetching releases', () => {
        it('rejects the Promise', () => {
          mock.onGet(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

          return Api.releases(dummyProjectPath).catch(() => {
            expect(mock.history.get).toHaveLength(1);
          });
        });
      });
    });

    describe('release', () => {
      const expectedUrl = `${baseReleaseUrl}/${encodeURIComponent(dummyTagName)}`;

      describe('when the release is successfully returned', () => {
        it('resolves the Promise', () => {
          mock.onGet(expectedUrl).replyOnce(httpStatus.OK);

          return Api.release(dummyProjectPath, dummyTagName).then(() => {
            expect(mock.history.get).toHaveLength(1);
          });
        });
      });

      describe('when an error occurs while fetching the release', () => {
        it('rejects the Promise', () => {
          mock.onGet(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

          return Api.release(dummyProjectPath, dummyTagName).catch(() => {
            expect(mock.history.get).toHaveLength(1);
          });
        });
      });
    });

    describe('createRelease', () => {
      const expectedUrl = baseReleaseUrl;

      const release = {
        name: 'Version 1.0',
      };

      describe('when the release is successfully created', () => {
        it('resolves the Promise', () => {
          mock.onPost(expectedUrl, release).replyOnce(httpStatus.CREATED);

          return Api.createRelease(dummyProjectPath, release).then(() => {
            expect(mock.history.post).toHaveLength(1);
          });
        });
      });

      describe('when an error occurs while creating the release', () => {
        it('rejects the Promise', () => {
          mock.onPost(expectedUrl, release).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

          return Api.createRelease(dummyProjectPath, release).catch(() => {
            expect(mock.history.post).toHaveLength(1);
          });
        });
      });
    });

    describe('updateRelease', () => {
      const expectedUrl = `${baseReleaseUrl}/${encodeURIComponent(dummyTagName)}`;

      const release = {
        name: 'Version 1.0',
      };

      describe('when the release is successfully updated', () => {
        it('resolves the Promise', () => {
          mock.onPut(expectedUrl, release).replyOnce(httpStatus.OK);

          return Api.updateRelease(dummyProjectPath, dummyTagName, release).then(() => {
            expect(mock.history.put).toHaveLength(1);
          });
        });
      });

      describe('when an error occurs while updating the release', () => {
        it('rejects the Promise', () => {
          mock.onPut(expectedUrl, release).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

          return Api.updateRelease(dummyProjectPath, dummyTagName, release).catch(() => {
            expect(mock.history.put).toHaveLength(1);
          });
        });
      });
    });

    describe('createReleaseLink', () => {
      const expectedUrl = `${baseReleaseUrl}/${dummyTagName}/assets/links`;
      const expectedLink = {
        url: 'https://example.com',
        name: 'An example link',
      };

      describe('when the Release is successfully created', () => {
        it('resolves the Promise', () => {
          mock.onPost(expectedUrl, expectedLink).replyOnce(httpStatus.CREATED);

          return Api.createReleaseLink(dummyProjectPath, dummyTagName, expectedLink).then(() => {
            expect(mock.history.post).toHaveLength(1);
          });
        });
      });

      describe('when an error occurs while creating the Release', () => {
        it('rejects the Promise', () => {
          mock.onPost(expectedUrl, expectedLink).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

          return Api.createReleaseLink(dummyProjectPath, dummyTagName, expectedLink).catch(() => {
            expect(mock.history.post).toHaveLength(1);
          });
        });
      });
    });

    describe('deleteReleaseLink', () => {
      const dummyLinkId = '4';
      const expectedUrl = `${baseReleaseUrl}/${dummyTagName}/assets/links/${dummyLinkId}`;

      describe('when the Release is successfully deleted', () => {
        it('resolves the Promise', () => {
          mock.onDelete(expectedUrl).replyOnce(httpStatus.OK);

          return Api.deleteReleaseLink(dummyProjectPath, dummyTagName, dummyLinkId).then(() => {
            expect(mock.history.delete).toHaveLength(1);
          });
        });
      });

      describe('when an error occurs while deleting the Release', () => {
        it('rejects the Promise', () => {
          mock.onDelete(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

          return Api.deleteReleaseLink(dummyProjectPath, dummyTagName, dummyLinkId).catch(() => {
            expect(mock.history.delete).toHaveLength(1);
          });
        });
      });
    });
  });

  describe('getRawFile', () => {
    const dummyProjectPath = 'gitlab-org/gitlab';
    const dummyFilePath = 'doc/CONTRIBUTING.md';
    const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
      dummyProjectPath,
    )}/repository/files/${encodeURIComponent(dummyFilePath)}/raw`;

    describe('when the raw file is successfully fetched', () => {
      it('resolves the Promise', () => {
        mock.onGet(expectedUrl).replyOnce(httpStatus.OK);

        return Api.getRawFile(dummyProjectPath, dummyFilePath).then(() => {
          expect(mock.history.get).toHaveLength(1);
        });
      });
    });

    describe('when an error occurs while getting a raw file', () => {
      it('rejects the Promise', () => {
        mock.onPost(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

        return Api.getRawFile(dummyProjectPath, dummyFilePath).catch(() => {
          expect(mock.history.get).toHaveLength(1);
        });
      });
    });
  });

  describe('createProjectMergeRequest', () => {
    const dummyProjectPath = 'gitlab-org/gitlab';
    const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
      dummyProjectPath,
    )}/merge_requests`;
    const options = {
      source_branch: 'feature',
      target_branch: 'master',
      title: 'Add feature',
    };

    describe('when the merge request is successfully created', () => {
      it('resolves the Promise', () => {
        mock.onPost(expectedUrl, options).replyOnce(httpStatus.CREATED);

        return Api.createProjectMergeRequest(dummyProjectPath, options).then(() => {
          expect(mock.history.post).toHaveLength(1);
        });
      });
    });

    describe('when an error occurs while getting a raw file', () => {
      it('rejects the Promise', () => {
        mock.onPost(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR);

        return Api.createProjectMergeRequest(dummyProjectPath).catch(() => {
          expect(mock.history.post).toHaveLength(1);
        });
      });
    });
  });

  describe('updateIssue', () => {
    it('update an issue with the given payload', done => {
      const projectId = 8;
      const issue = 1;
      const expectedArray = [1, 2, 3];
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/issues/${issue}`;
      mock.onPut(expectedUrl).reply(httpStatus.OK, { assigneeIds: expectedArray });

      Api.updateIssue(projectId, issue, { assigneeIds: expectedArray })
        .then(({ data }) => {
          expect(data.assigneeIds).toEqual(expectedArray);
          done();
        })
        .catch(done.fail);
    });
  });

  describe('updateMergeRequest', () => {
    it('update an issue with the given payload', done => {
      const projectId = 8;
      const mergeRequest = 1;
      const expectedArray = [1, 2, 3];
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/merge_requests/${mergeRequest}`;
      mock.onPut(expectedUrl).reply(httpStatus.OK, { assigneeIds: expectedArray });

      Api.updateMergeRequest(projectId, mergeRequest, { assigneeIds: expectedArray })
        .then(({ data }) => {
          expect(data.assigneeIds).toEqual(expectedArray);
          done();
        })
        .catch(done.fail);
    });
  });

  describe('tags', () => {
    it('fetches all tags of a particular project', done => {
      const query = 'dummy query';
      const options = { unused: 'option' };
      const projectId = 8;
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/repository/tags`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [
        {
          name: 'test',
        },
      ]);

      Api.tags(projectId, query, options)
        .then(({ data }) => {
          expect(data.length).toBe(1);
          expect(data[0].name).toBe('test');
        })
        .then(done)
        .catch(done.fail);
    });
  });

  describe('freezePeriods', () => {
    it('fetches freezePeriods', () => {
      const projectId = 8;
      const freezePeriod = {
        id: 3,
        freeze_start: '5 4 * * *',
        freeze_end: '5 9 * 8 *',
        cron_timezone: 'America/New_York',
        created_at: '2020-07-10T05:10:35.122Z',
        updated_at: '2020-07-10T05:10:35.122Z',
      };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/freeze_periods`;
      mock.onGet(expectedUrl).reply(httpStatus.OK, [freezePeriod]);

      return Api.freezePeriods(projectId).then(({ data }) => {
        expect(data[0]).toStrictEqual(freezePeriod);
      });
    });
  });

  describe('createFreezePeriod', () => {
    const projectId = 8;
    const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/freeze_periods`;
    const options = {
      freeze_start: '* * * * *',
      freeze_end: '* * * * *',
      cron_timezone: 'America/Juneau',
    };

    const expectedResult = {
      id: 10,
      freeze_start: '* * * * *',
      freeze_end: '* * * * *',
      cron_timezone: 'America/Juneau',
      created_at: '2020-07-11T07:04:50.153Z',
      updated_at: '2020-07-11T07:04:50.153Z',
    };

    describe('when the freeze period is successfully created', () => {
      it('resolves the Promise', () => {
        mock.onPost(expectedUrl, options).replyOnce(httpStatus.CREATED, expectedResult);

        return Api.createFreezePeriod(projectId, options).then(({ data }) => {
          expect(data).toStrictEqual(expectedResult);
        });
      });
    });
  });

  describe('createPipeline', () => {
    it('creates new pipeline', () => {
      const redirectUrl = 'ci-project/-/pipelines/95';
      const projectId = 8;
      const postData = {
        ref: 'tag-1',
        variables: [
          { key: 'test_file', value: 'test_file_val', variable_type: 'file' },
          { key: 'test_var', value: 'test_var_val', variable_type: 'env_var' },
        ],
      };
      const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/pipeline`;

      jest.spyOn(axios, 'post');

      mock.onPost(expectedUrl).replyOnce(httpStatus.OK, {
        web_url: redirectUrl,
      });

      return Api.createPipeline(projectId, postData).then(({ data }) => {
        expect(data.web_url).toBe(redirectUrl);
        expect(axios.post).toHaveBeenCalledWith(expectedUrl, postData, {
          headers: {
            'Content-Type': 'application/json',
          },
        });
      });
    });
  });

  describe('trackRedisHllUserEvent', () => {
    const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/usage_data/increment_unique_users`;

    const event = 'dummy_event';
    const postData = { event };
    const headers = {
      'Content-Type': 'application/json',
    };

    describe('when usage data increment unique users is called with feature flag disabled', () => {
      beforeEach(() => {
        gon.features = { ...gon.features, usageDataApi: false };
      });

      it('returns null', () => {
        jest.spyOn(axios, 'post');
        mock.onPost(expectedUrl).replyOnce(httpStatus.OK, true);

        expect(axios.post).toHaveBeenCalledTimes(0);
        expect(Api.trackRedisHllUserEvent(event)).toEqual(null);
      });
    });

    describe('when usage data increment unique users is called', () => {
      beforeEach(() => {
        gon.features = { ...gon.features, usageDataApi: true };
      });

      it('resolves the Promise', () => {
        jest.spyOn(axios, 'post');
        mock.onPost(expectedUrl, { event }).replyOnce(httpStatus.OK, true);

        return Api.trackRedisHllUserEvent(event).then(({ data }) => {
          expect(data).toEqual(true);
          expect(axios.post).toHaveBeenCalledWith(expectedUrl, postData, { headers });
        });
      });
    });
  });

  describe('Feature Flag User List', () => {
    let expectedUrl;
    let projectId;
    let mockUserList;

    beforeEach(() => {
      projectId = 1000;
      expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/feature_flags_user_lists`;
      mockUserList = {
        name: 'mock_user_list',
        user_xids: '1,2,3,4',
        project_id: 1,
        id: 1,
        iid: 1,
      };
    });

    describe('fetchFeatureFlagUserLists', () => {
      it('GETs the right url', () => {
        mock.onGet(expectedUrl).replyOnce(httpStatus.OK, []);

        return Api.fetchFeatureFlagUserLists(projectId).then(({ data }) => {
          expect(data).toEqual([]);
        });
      });
    });

    describe('searchFeatureFlagUserLists', () => {
      it('GETs the right url', () => {
        mock.onGet(expectedUrl, { params: { search: 'test' } }).replyOnce(httpStatus.OK, []);

        return Api.searchFeatureFlagUserLists(projectId, 'test').then(({ data }) => {
          expect(data).toEqual([]);
        });
      });
    });

    describe('createFeatureFlagUserList', () => {
      it('POSTs data to the right url', () => {
        const mockUserListData = {
          name: 'mock_user_list',
          user_xids: '1,2,3,4',
        };
        mock.onPost(expectedUrl, mockUserListData).replyOnce(httpStatus.OK, mockUserList);

        return Api.createFeatureFlagUserList(projectId, mockUserListData).then(({ data }) => {
          expect(data).toEqual(mockUserList);
        });
      });
    });

    describe('fetchFeatureFlagUserList', () => {
      it('GETs the right url', () => {
        mock.onGet(`${expectedUrl}/1`).replyOnce(httpStatus.OK, mockUserList);

        return Api.fetchFeatureFlagUserList(projectId, 1).then(({ data }) => {
          expect(data).toEqual(mockUserList);
        });
      });
    });

    describe('updateFeatureFlagUserList', () => {
      it('PUTs the right url', () => {
        mock
          .onPut(`${expectedUrl}/1`)
          .replyOnce(httpStatus.OK, { ...mockUserList, user_xids: '5' });

        return Api.updateFeatureFlagUserList(projectId, {
          ...mockUserList,
          user_xids: '5',
        }).then(({ data }) => {
          expect(data).toEqual({ ...mockUserList, user_xids: '5' });
        });
      });
    });

    describe('deleteFeatureFlagUserList', () => {
      it('DELETEs the right url', () => {
        mock.onDelete(`${expectedUrl}/1`).replyOnce(httpStatus.OK, 'deleted');

        return Api.deleteFeatureFlagUserList(projectId, 1).then(({ data }) => {
          expect(data).toBe('deleted');
        });
      });
    });
  });
});
