feat(web): resolve walkthrough diffs for GitLab merge requests
This commit is contained in:
@@ -2,10 +2,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../github/octokit.js', () => ({ getOctokitOrNull: vi.fn() }));
|
||||
vi.mock('../github/repo/index.js', () => ({ resolveGitHubRepoFromDirectory: vi.fn() }));
|
||||
vi.mock('../gitlab/client.js', () => ({ getGitLabClientOrNull: vi.fn() }));
|
||||
vi.mock('../gitlab/repo.js', () => ({ resolveGitLabRepoFromDirectory: vi.fn() }));
|
||||
|
||||
const { getPullRequestDiff } = await import('./pull-request.js');
|
||||
const { getOctokitOrNull } = await import('../github/octokit.js');
|
||||
const { resolveGitHubRepoFromDirectory } = await import('../github/repo/index.js');
|
||||
const { getGitLabClientOrNull } = await import('../gitlab/client.js');
|
||||
const { resolveGitLabRepoFromDirectory } = await import('../gitlab/repo.js');
|
||||
|
||||
const PATCH = `diff --git a/src/a.ts b/src/a.ts
|
||||
--- a/src/a.ts
|
||||
@@ -14,6 +18,28 @@ const PATCH = `diff --git a/src/a.ts b/src/a.ts
|
||||
+const added = true;
|
||||
`;
|
||||
|
||||
const GITLAB_DIFF_ONE = `diff --git a/a.txt b/a.txt
|
||||
--- a/a.txt
|
||||
+++ b/a.txt
|
||||
@@ -1,1 +1,2 @@
|
||||
+hello
|
||||
`;
|
||||
|
||||
const GITLAB_DIFF_TWO = `diff --git a/b.txt b/b.txt
|
||||
--- a/b.txt
|
||||
+++ b/b.txt
|
||||
@@ -1 +1,2 @@
|
||||
+world
|
||||
`;
|
||||
|
||||
const GITLAB_REPO = {
|
||||
namespace: 'acme',
|
||||
project: 'widgets',
|
||||
host: 'gitlab.com',
|
||||
baseUrl: 'https://gitlab.com',
|
||||
url: 'https://gitlab.com/acme/widgets',
|
||||
};
|
||||
|
||||
describe('getPullRequestDiff', () => {
|
||||
let request;
|
||||
|
||||
@@ -27,6 +53,10 @@ describe('getPullRequestDiff', () => {
|
||||
repo: { owner: 'openchamber', repo: 'openchamber' },
|
||||
remoteUrl: 'git@github.com:openchamber/openchamber.git',
|
||||
});
|
||||
// Default to a non-GitLab directory so the GitHub cases keep routing to
|
||||
// the GitHub path.
|
||||
resolveGitLabRepoFromDirectory.mockResolvedValue({ repo: null, remoteUrl: null });
|
||||
getGitLabClientOrNull.mockReturnValue(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -74,4 +104,81 @@ describe('getPullRequestDiff', () => {
|
||||
statusCode: 404,
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a GitLab repository', () => {
|
||||
let mergeRequestDiffs;
|
||||
|
||||
beforeEach(() => {
|
||||
mergeRequestDiffs = vi.fn().mockResolvedValue({
|
||||
status: 200,
|
||||
data: [{ diff: GITLAB_DIFF_ONE }],
|
||||
page: { page: 1, next: null, total: 1, hasMore: false },
|
||||
});
|
||||
getGitLabClientOrNull.mockReturnValue({ mergeRequestDiffs });
|
||||
resolveGitLabRepoFromDirectory.mockResolvedValue({
|
||||
repo: GITLAB_REPO,
|
||||
remoteUrl: 'git@gitlab.com:acme/widgets.git',
|
||||
});
|
||||
});
|
||||
|
||||
it('concatenates merge request diffs across pages into a single patch', async () => {
|
||||
mergeRequestDiffs
|
||||
.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: [{ diff: GITLAB_DIFF_ONE }],
|
||||
page: { page: 1, next: 2, total: 2, hasMore: true },
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: [{ diff: GITLAB_DIFF_TWO }],
|
||||
page: { page: 2, next: null, total: 2, hasMore: false },
|
||||
});
|
||||
|
||||
const result = await getPullRequestDiff('/repo', 7);
|
||||
|
||||
expect(result.patch).toBe(`${GITLAB_DIFF_ONE}\n${GITLAB_DIFF_TWO}`);
|
||||
expect(result.meta).toEqual({ namespace: 'acme', project: 'widgets', number: 7 });
|
||||
// The unencoded namespace/project path is passed to the client, which
|
||||
// URL-encodes it internally.
|
||||
expect(mergeRequestDiffs).toHaveBeenCalledTimes(2);
|
||||
expect(mergeRequestDiffs).toHaveBeenNthCalledWith(1, 'acme/widgets', 7, { per_page: 100, page: 1 });
|
||||
expect(mergeRequestDiffs).toHaveBeenNthCalledWith(2, 'acme/widgets', 7, { per_page: 100, page: 2 });
|
||||
});
|
||||
|
||||
it('asks the user to connect GitLab before fetching diffs', async () => {
|
||||
getGitLabClientOrNull.mockReturnValue(null);
|
||||
|
||||
await expect(getPullRequestDiff('/repo', 7)).rejects.toMatchObject({
|
||||
code: 'gitlab-not-connected',
|
||||
statusCode: 401,
|
||||
});
|
||||
expect(mergeRequestDiffs).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('treats an MR with no diff as missing rather than an empty review', async () => {
|
||||
mergeRequestDiffs.mockResolvedValue({
|
||||
status: 200,
|
||||
data: [{ diff: ' ' }],
|
||||
page: { page: 1, next: null, total: 1, hasMore: false },
|
||||
});
|
||||
|
||||
await expect(getPullRequestDiff('/repo', 7)).rejects.toMatchObject({
|
||||
code: 'empty-diff',
|
||||
statusCode: 404,
|
||||
});
|
||||
});
|
||||
|
||||
it('reports a GitLab repo without namespace or project as having no remote', async () => {
|
||||
resolveGitLabRepoFromDirectory.mockResolvedValue({
|
||||
repo: { namespace: '', project: '' },
|
||||
remoteUrl: 'git@gitlab.com:acme/widgets.git',
|
||||
});
|
||||
|
||||
await expect(getPullRequestDiff('/repo', 7)).rejects.toMatchObject({
|
||||
code: 'no-gitlab-remote',
|
||||
statusCode: 400,
|
||||
});
|
||||
expect(mergeRequestDiffs).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user