mirror of https://github.com/renovatebot/renovate
2993 lines
86 KiB
TypeScript
2993 lines
86 KiB
TypeScript
import type { EnsureIssueConfig, Platform, RepoParams } from '..';
|
|
import * as httpMock from '../../../../test/http-mock';
|
|
import { mocked, partial } from '../../../../test/util';
|
|
import {
|
|
CONFIG_GIT_URL_UNAVAILABLE,
|
|
REPOSITORY_ACCESS_FORBIDDEN,
|
|
REPOSITORY_ARCHIVED,
|
|
REPOSITORY_BLOCKED,
|
|
REPOSITORY_CHANGED,
|
|
REPOSITORY_EMPTY,
|
|
REPOSITORY_MIRRORED,
|
|
} from '../../../constants/error-messages';
|
|
import type { logger as _logger } from '../../../logger';
|
|
import type * as _git from '../../../util/git';
|
|
import type { LongCommitSha } from '../../../util/git/types';
|
|
import { setBaseUrl } from '../../../util/http/gitea';
|
|
import type {
|
|
Comment,
|
|
CommitStatus,
|
|
CommitStatusType,
|
|
Issue,
|
|
Label,
|
|
PR,
|
|
Repo,
|
|
RepoPermission,
|
|
User,
|
|
} from './types';
|
|
|
|
jest.mock('../../../util/git');
|
|
|
|
/**
|
|
* latest tested gitea version.
|
|
*/
|
|
const GITEA_VERSION = '1.14.0+dev-754-g5d2b7ba63';
|
|
|
|
describe('modules/platform/gitea/index', () => {
|
|
let gitea: Platform;
|
|
let logger: jest.Mocked<typeof _logger>;
|
|
let git: jest.Mocked<typeof _git>;
|
|
let hostRules: typeof import('../../../util/host-rules');
|
|
let memCache: typeof import('../../../util/cache/memory');
|
|
|
|
function mockedRepo(opts: Partial<Repo>): Repo {
|
|
return partial<Repo>({
|
|
permissions: partial<RepoPermission>({ push: true, pull: true }),
|
|
has_pull_requests: true,
|
|
...opts,
|
|
});
|
|
}
|
|
|
|
const mockCommitHash =
|
|
'0d9c7726c3d628b7e28af234595cfd20febdbf8e' as LongCommitSha;
|
|
|
|
const mockUser: User = {
|
|
id: 1,
|
|
username: 'renovate',
|
|
full_name: 'Renovate Bot',
|
|
email: 'renovate@example.com',
|
|
};
|
|
|
|
const mockRepo = mockedRepo({
|
|
allow_rebase: true,
|
|
clone_url: 'https://gitea.renovatebot.com/some/repo.git',
|
|
ssh_url: 'git@gitea.renovatebot.com/some/repo.git',
|
|
default_branch: 'master',
|
|
full_name: 'some/repo',
|
|
});
|
|
|
|
type MockPr = PR & Required<Pick<PR, 'head' | 'base'>>;
|
|
|
|
const mockRepos: Repo[] = [
|
|
mockedRepo({ full_name: 'a/b' }),
|
|
mockedRepo({ full_name: 'c/d' }),
|
|
mockedRepo({ full_name: 'e/f', mirror: true }),
|
|
];
|
|
|
|
const mockTopicRepos: Repo[] = [mockedRepo({ full_name: 'a/b' })];
|
|
|
|
const mockNamespaceRepos: Repo[] = [
|
|
mockedRepo({ full_name: 'org1/repo' }),
|
|
mockedRepo({ full_name: 'org2/repo' }),
|
|
mockedRepo({ full_name: 'org2/repo2', archived: true }),
|
|
];
|
|
|
|
const mockPRs: MockPr[] = [
|
|
partial<MockPr>({
|
|
number: 1,
|
|
title: 'Some PR',
|
|
body: 'some random pull request',
|
|
state: 'open',
|
|
diff_url: 'https://gitea.renovatebot.com/some/repo/pulls/1.diff',
|
|
created_at: '2015-03-22T20:36:16Z',
|
|
closed_at: '2015-03-22T21:36:16Z',
|
|
updated_at: '2015-03-22T21:36:16Z',
|
|
mergeable: true,
|
|
base: { ref: 'some-base-branch' },
|
|
head: {
|
|
label: 'some-head-branch',
|
|
sha: 'some-head-sha' as LongCommitSha,
|
|
repo: partial<Repo>({ full_name: mockRepo.full_name }),
|
|
},
|
|
}),
|
|
partial<MockPr>({
|
|
number: 2,
|
|
title: 'Other PR',
|
|
body: 'other random pull request',
|
|
state: 'closed',
|
|
diff_url: 'https://gitea.renovatebot.com/some/repo/pulls/2.diff',
|
|
created_at: '2011-08-18T22:30:38Z',
|
|
closed_at: '2016-01-09T10:03:21Z',
|
|
updated_at: '2016-01-09T10:03:21Z',
|
|
mergeable: true,
|
|
base: { ref: 'other-base-branch' },
|
|
head: {
|
|
label: 'other-head-branch',
|
|
sha: 'other-head-sha' as LongCommitSha,
|
|
repo: partial<Repo>({ full_name: mockRepo.full_name }),
|
|
},
|
|
labels: [
|
|
{
|
|
id: 1,
|
|
name: 'bug',
|
|
},
|
|
],
|
|
}),
|
|
partial<MockPr>({
|
|
number: 3,
|
|
title: 'WIP: Draft PR',
|
|
body: 'other random pull request',
|
|
state: 'open',
|
|
diff_url: 'https://gitea.renovatebot.com/some/repo/pulls/3.diff',
|
|
created_at: '2011-08-18T22:30:39Z',
|
|
closed_at: '2016-01-09T10:03:22Z',
|
|
updated_at: '2017-01-09T10:03:22Z',
|
|
mergeable: false,
|
|
base: { ref: 'draft-base-branch' },
|
|
head: {
|
|
label: 'draft-head-branch',
|
|
sha: 'draft-head-sha' as LongCommitSha,
|
|
repo: partial<Repo>({ full_name: mockRepo.full_name }),
|
|
},
|
|
}),
|
|
partial<MockPr>({
|
|
number: 4,
|
|
title: 'Merged PR',
|
|
body: 'other random merged pull request',
|
|
state: 'closed',
|
|
diff_url: 'https://gitea.renovatebot.com/some/repo/pulls/4.diff',
|
|
created_at: '2011-08-18T22:30:38Z',
|
|
closed_at: '2016-01-09T10:03:21Z',
|
|
updated_at: '2016-01-09T10:03:21Z',
|
|
mergeable: true,
|
|
merged: true,
|
|
base: { ref: 'other-base-branch' },
|
|
head: {
|
|
label: 'merged-head-branch',
|
|
sha: 'merged-head-sha' as LongCommitSha,
|
|
repo: partial<Repo>({ full_name: mockRepo.full_name }),
|
|
},
|
|
labels: [
|
|
{
|
|
id: 1,
|
|
name: 'bug',
|
|
},
|
|
],
|
|
}),
|
|
];
|
|
|
|
const mockIssues: Issue[] = [
|
|
{
|
|
number: 1,
|
|
title: 'open-issue',
|
|
state: 'open',
|
|
body: 'some-content',
|
|
assignees: [],
|
|
labels: [],
|
|
},
|
|
{
|
|
number: 2,
|
|
title: 'closed-issue',
|
|
state: 'closed',
|
|
body: 'other-content',
|
|
assignees: [],
|
|
labels: undefined as never, // coverage
|
|
},
|
|
{
|
|
number: 3,
|
|
title: 'duplicate-issue',
|
|
state: 'open',
|
|
body: 'duplicate-content',
|
|
assignees: [],
|
|
labels: [],
|
|
},
|
|
{
|
|
number: 4,
|
|
title: 'duplicate-issue',
|
|
state: 'open',
|
|
body: 'duplicate-content',
|
|
assignees: [],
|
|
labels: [],
|
|
},
|
|
{
|
|
number: 5,
|
|
title: 'duplicate-issue',
|
|
state: 'open',
|
|
body: 'duplicate-content',
|
|
assignees: [],
|
|
labels: [],
|
|
},
|
|
];
|
|
|
|
const mockComments: Comment[] = [
|
|
{ id: 11, body: 'some-body' },
|
|
{ id: 12, body: 'other-body' },
|
|
{ id: 13, body: '### some-topic\n\nsome-content' },
|
|
];
|
|
|
|
const mockRepoLabels: Label[] = [
|
|
{ id: 1, name: 'some-label', description: 'its a me', color: '#000000' },
|
|
{ id: 2, name: 'other-label', description: 'labelario', color: '#ffffff' },
|
|
];
|
|
|
|
const mockOrgLabels: Label[] = [
|
|
{
|
|
id: 3,
|
|
name: 'some-org-label',
|
|
description: 'its a org me',
|
|
color: '#0000aa',
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'other-org-label',
|
|
description: 'org labelario',
|
|
color: '#ffffaa',
|
|
},
|
|
];
|
|
|
|
beforeEach(async () => {
|
|
jest.resetModules();
|
|
|
|
memCache = await import('../../../util/cache/memory');
|
|
gitea = await import('.');
|
|
logger = mocked(await import('../../../logger')).logger;
|
|
git = jest.requireMock('../../../util/git');
|
|
git.isBranchBehindBase.mockResolvedValue(false);
|
|
git.getBranchCommit.mockReturnValue(mockCommitHash);
|
|
hostRules = await import('../../../util/host-rules');
|
|
hostRules.clear();
|
|
|
|
setBaseUrl('https://gitea.renovatebot.com/');
|
|
});
|
|
|
|
async function initFakePlatform(
|
|
scope: httpMock.Scope,
|
|
version = GITEA_VERSION,
|
|
): Promise<void> {
|
|
scope
|
|
.get('/user')
|
|
.reply(200, mockUser)
|
|
.get('/version')
|
|
.reply(200, { version });
|
|
await gitea.initPlatform({ token: 'abc' });
|
|
}
|
|
|
|
async function initFakeRepo(
|
|
scope: httpMock.Scope,
|
|
repo?: Partial<Repo>,
|
|
config?: Partial<RepoParams>,
|
|
): Promise<void> {
|
|
const repoResult = { ...mockRepo, ...repo };
|
|
const repository = repoResult.full_name;
|
|
scope.get(`/repos/${repository}`).reply(200, repoResult);
|
|
await gitea.initRepo({ repository, ...config });
|
|
}
|
|
|
|
describe('initPlatform()', () => {
|
|
it('should throw if no token', async () => {
|
|
await expect(gitea.initPlatform({})).rejects.toThrow();
|
|
});
|
|
|
|
it('should throw if auth fails', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
scope.get('/user').reply(500);
|
|
|
|
await expect(
|
|
gitea.initPlatform({ token: 'some-token' }),
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
it('should support default endpoint', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
scope
|
|
.get('/user')
|
|
.reply(200, mockUser)
|
|
.get('/version')
|
|
.reply(200, { version: GITEA_VERSION });
|
|
|
|
expect(await gitea.initPlatform({ token: 'some-token' })).toEqual({
|
|
endpoint: 'https://gitea.com/',
|
|
gitAuthor: 'Renovate Bot <renovate@example.com>',
|
|
});
|
|
});
|
|
|
|
it('should support custom endpoint', async () => {
|
|
const scope = httpMock.scope('https://gitea.renovatebot.com/api/v1');
|
|
scope
|
|
.get('/user')
|
|
.reply(200, mockUser)
|
|
.get('/version')
|
|
.reply(200, { version: GITEA_VERSION });
|
|
|
|
expect(
|
|
await gitea.initPlatform({
|
|
token: 'some-token',
|
|
endpoint: 'https://gitea.renovatebot.com',
|
|
}),
|
|
).toEqual({
|
|
endpoint: 'https://gitea.renovatebot.com/',
|
|
gitAuthor: 'Renovate Bot <renovate@example.com>',
|
|
});
|
|
});
|
|
|
|
it('should support custom endpoint including api path', async () => {
|
|
const scope = httpMock.scope('https://gitea.renovatebot.com/api/v1');
|
|
scope
|
|
.get('/user')
|
|
.reply(200, mockUser)
|
|
.get('/version')
|
|
.reply(200, { version: GITEA_VERSION });
|
|
|
|
expect(
|
|
await gitea.initPlatform({
|
|
token: 'some-token',
|
|
endpoint: 'https://gitea.renovatebot.com',
|
|
}),
|
|
).toEqual({
|
|
endpoint: 'https://gitea.renovatebot.com/',
|
|
gitAuthor: 'Renovate Bot <renovate@example.com>',
|
|
});
|
|
});
|
|
|
|
it('should use username as author name if full name is missing', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
scope
|
|
.get('/user')
|
|
.reply(200, {
|
|
...mockUser,
|
|
full_name: undefined,
|
|
})
|
|
.get('/version')
|
|
.reply(200, { version: GITEA_VERSION });
|
|
|
|
expect(await gitea.initPlatform({ token: 'some-token' })).toEqual({
|
|
endpoint: 'https://gitea.com/',
|
|
gitAuthor: 'renovate <renovate@example.com>',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getRepos', () => {
|
|
it('should propagate any other errors', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/search')
|
|
.query({
|
|
uid: 1,
|
|
archived: false,
|
|
})
|
|
.replyWithError(new Error('searchRepos()'));
|
|
await initFakePlatform(scope);
|
|
|
|
await expect(gitea.getRepos()).rejects.toThrow('searchRepos()');
|
|
});
|
|
|
|
it('should return an array of repos', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/search')
|
|
.query({
|
|
uid: 1,
|
|
archived: false,
|
|
})
|
|
.reply(200, {
|
|
ok: true,
|
|
data: mockRepos,
|
|
});
|
|
await initFakePlatform(scope);
|
|
|
|
const repos = await gitea.getRepos();
|
|
expect(repos).toEqual(['a/b', 'c/d']);
|
|
});
|
|
|
|
it('should return an filtered array of repos', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
|
|
scope
|
|
.get('/repos/search')
|
|
.query({
|
|
uid: 1,
|
|
archived: false,
|
|
q: 'renovate',
|
|
topic: true,
|
|
})
|
|
.reply(200, {
|
|
ok: true,
|
|
data: mockTopicRepos,
|
|
});
|
|
|
|
scope
|
|
.get('/repos/search')
|
|
.query({
|
|
uid: 1,
|
|
archived: false,
|
|
q: 'renovatebot',
|
|
topic: true,
|
|
})
|
|
.reply(200, {
|
|
ok: true,
|
|
data: mockTopicRepos,
|
|
});
|
|
|
|
await initFakePlatform(scope);
|
|
|
|
const repos = await gitea.getRepos({
|
|
topics: ['renovate', 'renovatebot'],
|
|
});
|
|
expect(repos).toEqual(['a/b']);
|
|
});
|
|
|
|
it('should query the organization endpoint for each namespace', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
|
|
scope.get('/orgs/org1/repos').reply(200, mockNamespaceRepos);
|
|
scope.get('/orgs/org2/repos').reply(200, mockNamespaceRepos);
|
|
|
|
await initFakePlatform(scope);
|
|
|
|
const repos = await gitea.getRepos({
|
|
namespaces: ['org1', 'org2'],
|
|
});
|
|
expect(repos).toEqual(['org1/repo', 'org2/repo']);
|
|
});
|
|
|
|
it('Sorts repos', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/search')
|
|
.query({
|
|
uid: 1,
|
|
archived: false,
|
|
sort: 'updated',
|
|
order: 'desc',
|
|
})
|
|
.reply(200, {
|
|
ok: true,
|
|
data: mockRepos,
|
|
});
|
|
await initFakePlatform(scope);
|
|
|
|
const repos = await gitea.getRepos({
|
|
sort: 'updated',
|
|
order: 'desc',
|
|
});
|
|
expect(repos).toEqual(['a/b', 'c/d']);
|
|
});
|
|
});
|
|
|
|
describe('initRepo', () => {
|
|
const initRepoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
};
|
|
|
|
it('should propagate API errors', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.replyWithError(new Error('getRepo()'));
|
|
await initFakePlatform(scope);
|
|
await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow('getRepo()');
|
|
});
|
|
|
|
it('should abort when repo is archived', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
archived: true,
|
|
});
|
|
await initFakePlatform(scope);
|
|
await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow(
|
|
REPOSITORY_ARCHIVED,
|
|
);
|
|
});
|
|
|
|
it('should abort when repo is mirrored', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
mirror: true,
|
|
});
|
|
await initFakePlatform(scope);
|
|
await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow(
|
|
REPOSITORY_MIRRORED,
|
|
);
|
|
});
|
|
|
|
it('should abort when repo is empty', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
empty: true,
|
|
});
|
|
await initFakePlatform(scope);
|
|
await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow(
|
|
REPOSITORY_EMPTY,
|
|
);
|
|
});
|
|
|
|
it('should abort when repo has insufficient permissions', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
permissions: {
|
|
pull: false,
|
|
push: false,
|
|
admin: false,
|
|
},
|
|
});
|
|
await initFakePlatform(scope);
|
|
await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow(
|
|
REPOSITORY_ACCESS_FORBIDDEN,
|
|
);
|
|
});
|
|
|
|
it('should abort when repo has pulls disabled', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
has_pull_requests: false,
|
|
});
|
|
await initFakePlatform(scope);
|
|
await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow(
|
|
REPOSITORY_BLOCKED,
|
|
);
|
|
});
|
|
|
|
it('should abort when repo has no available merge methods', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
allow_rebase: false,
|
|
});
|
|
await initFakePlatform(scope);
|
|
await expect(gitea.initRepo(initRepoCfg)).rejects.toThrow(
|
|
REPOSITORY_BLOCKED,
|
|
);
|
|
});
|
|
|
|
it('should fall back to merge method "rebase-merge"', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
allow_rebase: false,
|
|
allow_rebase_explicit: true,
|
|
});
|
|
await initFakePlatform(scope);
|
|
|
|
await gitea.initRepo(initRepoCfg);
|
|
|
|
expect(git.initRepo).toHaveBeenCalledExactlyOnceWith(
|
|
expect.objectContaining({
|
|
mergeMethod: 'rebase-merge',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should fall back to merge method "squash"', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
allow_rebase: false,
|
|
allow_squash_merge: true,
|
|
});
|
|
await initFakePlatform(scope);
|
|
|
|
await gitea.initRepo(initRepoCfg);
|
|
|
|
expect(git.initRepo).toHaveBeenCalledExactlyOnceWith(
|
|
expect.objectContaining({
|
|
mergeMethod: 'squash',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should fall back to merge method "merge"', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
allow_rebase: false,
|
|
allow_merge_commits: true,
|
|
});
|
|
await initFakePlatform(scope);
|
|
|
|
await gitea.initRepo(initRepoCfg);
|
|
|
|
expect(git.initRepo).toHaveBeenCalledExactlyOnceWith(
|
|
expect.objectContaining({
|
|
mergeMethod: 'merge',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should use clone_url of repo if gitUrl is not specified', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, mockRepo);
|
|
await initFakePlatform(scope);
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
};
|
|
await gitea.initRepo(repoCfg);
|
|
|
|
expect(git.initRepo).toHaveBeenCalledWith(
|
|
expect.objectContaining({ url: mockRepo.clone_url }),
|
|
);
|
|
});
|
|
|
|
it('should use clone_url of repo if gitUrl has value default', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, mockRepo);
|
|
await initFakePlatform(scope);
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
gitUrl: 'default',
|
|
};
|
|
await gitea.initRepo(repoCfg);
|
|
|
|
expect(git.initRepo).toHaveBeenCalledWith(
|
|
expect.objectContaining({ url: mockRepo.clone_url }),
|
|
);
|
|
});
|
|
|
|
it('should use ssh_url of repo if gitUrl has value ssh', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, mockRepo);
|
|
await initFakePlatform(scope);
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
gitUrl: 'ssh',
|
|
};
|
|
await gitea.initRepo(repoCfg);
|
|
|
|
expect(git.initRepo).toHaveBeenCalledWith(
|
|
expect.objectContaining({ url: mockRepo.ssh_url }),
|
|
);
|
|
});
|
|
|
|
it('should abort when gitUrl has value ssh but ssh_url is empty', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, { ...mockRepo, ssh_url: undefined });
|
|
await initFakePlatform(scope);
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
gitUrl: 'ssh',
|
|
};
|
|
|
|
await expect(gitea.initRepo(repoCfg)).rejects.toThrow(
|
|
CONFIG_GIT_URL_UNAVAILABLE,
|
|
);
|
|
});
|
|
|
|
it('should use generated url of repo if gitUrl has value endpoint', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, mockRepo);
|
|
await initFakePlatform(scope);
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
gitUrl: 'endpoint',
|
|
};
|
|
await gitea.initRepo(repoCfg);
|
|
|
|
expect(git.initRepo).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
url: `https://gitea.com/${mockRepo.full_name}.git`,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should abort when clone_url is empty', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
clone_url: undefined,
|
|
});
|
|
await initFakePlatform(scope);
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
};
|
|
|
|
await expect(gitea.initRepo(repoCfg)).rejects.toThrow(
|
|
CONFIG_GIT_URL_UNAVAILABLE,
|
|
);
|
|
});
|
|
|
|
it('should use given access token if gitUrl has value endpoint', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, mockRepo);
|
|
await initFakePlatform(scope);
|
|
|
|
const token = 'abc';
|
|
hostRules.add({
|
|
hostType: 'gitea',
|
|
matchHost: 'https://gitea.com/',
|
|
token,
|
|
});
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
gitUrl: 'endpoint',
|
|
};
|
|
await gitea.initRepo(repoCfg);
|
|
|
|
const url = new URL(`${mockRepo.clone_url}`);
|
|
url.username = token;
|
|
expect(git.initRepo).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
url: `https://${token}@gitea.com/${mockRepo.full_name}.git`,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should use given access token if gitUrl is not specified', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, mockRepo);
|
|
await initFakePlatform(scope);
|
|
|
|
const token = 'abc';
|
|
hostRules.add({
|
|
hostType: 'gitea',
|
|
matchHost: 'https://gitea.com/',
|
|
token,
|
|
});
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
};
|
|
await gitea.initRepo(repoCfg);
|
|
|
|
const url = new URL(`${mockRepo.clone_url}`);
|
|
url.username = token;
|
|
expect(git.initRepo).toHaveBeenCalledWith(
|
|
expect.objectContaining({ url: url.toString() }),
|
|
);
|
|
});
|
|
|
|
it('should abort when clone_url is not valid', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get(`/repos/${initRepoCfg.repository}`)
|
|
.reply(200, {
|
|
...mockRepo,
|
|
clone_url: 'abc',
|
|
});
|
|
await initFakePlatform(scope);
|
|
|
|
const repoCfg: RepoParams = {
|
|
repository: mockRepo.full_name,
|
|
};
|
|
|
|
await expect(gitea.initRepo(repoCfg)).rejects.toThrow(
|
|
CONFIG_GIT_URL_UNAVAILABLE,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('setBranchStatus', () => {
|
|
it('should create a new commit status', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post(
|
|
'/repos/some/repo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e',
|
|
{
|
|
state: 'success',
|
|
context: 'some-context',
|
|
description: 'some-description',
|
|
},
|
|
)
|
|
.reply(200)
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, []);
|
|
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.setBranchStatus({
|
|
branchName: 'some-branch',
|
|
state: 'green',
|
|
context: 'some-context',
|
|
description: 'some-description',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should default to pending state', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post(
|
|
'/repos/some/repo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e',
|
|
{
|
|
state: 'pending',
|
|
context: 'some-context',
|
|
description: 'some-description',
|
|
},
|
|
)
|
|
.reply(200)
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, []);
|
|
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.setBranchStatus({
|
|
branchName: 'some-branch',
|
|
context: 'some-context',
|
|
description: 'some-description',
|
|
state: undefined as never,
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should include url if specified', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post(
|
|
'/repos/some/repo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e',
|
|
{
|
|
state: 'success',
|
|
context: 'some-context',
|
|
description: 'some-description',
|
|
target_url: 'some-url',
|
|
},
|
|
)
|
|
.reply(200)
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, []);
|
|
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.setBranchStatus({
|
|
branchName: 'some-branch',
|
|
state: 'green',
|
|
context: 'some-context',
|
|
description: 'some-description',
|
|
url: 'some-url',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should gracefully fail with warning', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post(
|
|
'/repos/some/repo/statuses/0d9c7726c3d628b7e28af234595cfd20febdbf8e',
|
|
)
|
|
.replyWithError('unknown error');
|
|
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.setBranchStatus({
|
|
branchName: 'some-branch',
|
|
state: 'green',
|
|
context: 'some-context',
|
|
description: 'some-description',
|
|
}),
|
|
).toResolve();
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
{
|
|
err: expect.any(Error),
|
|
},
|
|
'Failed to set branch status',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getBranchStatus', () => {
|
|
const commitStatus = (status: CommitStatusType): CommitStatus => ({
|
|
id: 1,
|
|
status,
|
|
context: '',
|
|
description: '',
|
|
target_url: '',
|
|
created_at: '',
|
|
});
|
|
|
|
it('should return yellow for unknown result', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [commitStatus('unknown')]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatus('some-branch', true);
|
|
|
|
expect(res).toBe('yellow');
|
|
});
|
|
|
|
it('should return pending state for pending result', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [commitStatus('pending')]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatus('some-branch', true);
|
|
|
|
expect(res).toBe('yellow');
|
|
});
|
|
|
|
it('should return green state for success result', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [commitStatus('success')]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatus('some-branch', true);
|
|
|
|
expect(res).toBe('green');
|
|
});
|
|
|
|
it('should return yellow for all other results', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [commitStatus('invalid' as never)]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatus('some-branch', true);
|
|
|
|
expect(res).toBe('yellow');
|
|
});
|
|
|
|
it('should abort when branch status returns 404', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(404);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.getBranchStatus('some-branch', true)).rejects.toThrow(
|
|
REPOSITORY_CHANGED,
|
|
);
|
|
});
|
|
|
|
it('should propagate any other errors', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.replyWithError('unknown error');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.getBranchStatus('some-branch', true)).rejects.toThrow(
|
|
'unknown error',
|
|
);
|
|
});
|
|
|
|
it('should treat internal checks as success', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [
|
|
{
|
|
id: 1,
|
|
status: 'success',
|
|
context: 'renovate/stability-days',
|
|
description: 'internal check',
|
|
target_url: '',
|
|
created_at: '',
|
|
},
|
|
]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatus('some-branch', true);
|
|
|
|
expect(res).toBe('green');
|
|
});
|
|
|
|
it('should not treat internal checks as success', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [
|
|
{
|
|
id: 1,
|
|
status: 'success',
|
|
context: 'renovate/stability-days',
|
|
description: 'internal check',
|
|
target_url: '',
|
|
created_at: '',
|
|
},
|
|
]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatus('some-branch', false);
|
|
|
|
expect(res).toBe('yellow');
|
|
});
|
|
});
|
|
|
|
describe('getBranchStatusCheck', () => {
|
|
it('should return null with no results', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, []);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
expect(
|
|
await gitea.getBranchStatusCheck('some-branch', 'some-context'),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('should return null with no matching results', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [
|
|
{
|
|
id: 1,
|
|
status: 'success',
|
|
context: 'other-context',
|
|
description: 'internal check',
|
|
target_url: '',
|
|
created_at: '',
|
|
},
|
|
]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatusCheck(
|
|
'some-branch',
|
|
'some-context',
|
|
);
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('should return yellow with unknown status', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [
|
|
{
|
|
id: 1,
|
|
status: 'xyz',
|
|
context: 'some-context',
|
|
description: '',
|
|
target_url: '',
|
|
created_at: '',
|
|
},
|
|
]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatusCheck(
|
|
'some-branch',
|
|
'some-context',
|
|
);
|
|
|
|
expect(res).toBe('yellow');
|
|
});
|
|
|
|
it('should return green of matching result', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/commits/some-branch/statuses')
|
|
.reply(200, [
|
|
{
|
|
id: 1,
|
|
status: 'success',
|
|
context: 'some-context',
|
|
description: '',
|
|
target_url: '',
|
|
created_at: '',
|
|
},
|
|
]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchStatusCheck(
|
|
'some-branch',
|
|
'some-context',
|
|
);
|
|
|
|
expect(res).toBe('green');
|
|
});
|
|
});
|
|
|
|
describe('getPrList', () => {
|
|
beforeEach(() => {
|
|
memCache.init();
|
|
});
|
|
|
|
it('should return list of pull requests', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getPrList();
|
|
expect(res).toMatchObject([
|
|
{ number: 1, title: 'Some PR' },
|
|
{ number: 2, title: 'Other PR' },
|
|
{ number: 3, title: 'Draft PR' },
|
|
{ number: 4, title: 'Merged PR' },
|
|
]);
|
|
});
|
|
|
|
it('should filter list by creator', async () => {
|
|
const thirdPartyPr = partial<PR>({
|
|
number: 42,
|
|
title: 'Third-party PR',
|
|
body: 'other random pull request',
|
|
state: 'open',
|
|
diff_url: 'https://gitea.renovatebot.com/some/repo/pulls/3.diff',
|
|
created_at: '2011-08-18T22:30:38Z',
|
|
closed_at: '2016-01-09T10:03:21Z',
|
|
mergeable: true,
|
|
base: { ref: 'third-party-base-branch' },
|
|
head: {
|
|
label: 'other-head-branch',
|
|
sha: 'other-head-sha' as LongCommitSha,
|
|
repo: partial<Repo>({ full_name: mockRepo.full_name }),
|
|
},
|
|
user: { username: 'not-renovate' },
|
|
});
|
|
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, [
|
|
thirdPartyPr,
|
|
...mockPRs.map((pr) => ({
|
|
...pr,
|
|
user: { username: 'renovate' },
|
|
})),
|
|
]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getPrList();
|
|
|
|
expect(res).toMatchObject([
|
|
{ number: 1, title: 'Some PR' },
|
|
{ number: 2, title: 'Other PR' },
|
|
{ number: 3, title: 'Draft PR' },
|
|
{ number: 4, title: 'Merged PR' },
|
|
]);
|
|
});
|
|
|
|
it('should cache results after first query', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res1 = await gitea.getPrList();
|
|
const res2 = await gitea.getPrList();
|
|
|
|
expect(res1).toEqual(res2);
|
|
});
|
|
|
|
it('should update cache results', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs.slice(0, 2))
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs.slice(1));
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res1 = await gitea.getPrList();
|
|
expect(res1).toMatchObject([{ number: 1 }, { number: 2 }]);
|
|
|
|
memCache.set('gitea-pr-cache-synced', false);
|
|
|
|
const res2 = await gitea.getPrList();
|
|
expect(res2).toMatchObject([
|
|
{ number: 1 },
|
|
{ number: 2 },
|
|
{ number: 3 },
|
|
{ number: 4 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('getPr', () => {
|
|
beforeEach(() => {
|
|
memCache.init();
|
|
});
|
|
|
|
it('should return enriched pull request which exists if open', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getPr(1);
|
|
|
|
expect(res).toMatchObject({ number: 1, title: 'Some PR' });
|
|
});
|
|
|
|
it('should fallback to direct fetching if cache fails', async () => {
|
|
const pr = mockPRs.find((pr) => pr.number === 1);
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, [])
|
|
.get('/repos/some/repo/pulls/1')
|
|
.reply(200, pr);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getPr(1);
|
|
|
|
expect(res).toMatchObject({ number: 1, title: 'Some PR' });
|
|
});
|
|
|
|
it('should return null for missing pull request', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, [])
|
|
.get('/repos/some/repo/pulls/42')
|
|
.reply(200); // TODO: 404 should be handled
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getPr(42);
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('findPr', () => {
|
|
it('should find pull request without title or state', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({ branchName: 'some-head-branch' });
|
|
|
|
expect(res).toMatchObject({
|
|
number: 1,
|
|
sourceBranch: 'some-head-branch',
|
|
});
|
|
});
|
|
|
|
it('should find pull request with title', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({
|
|
branchName: 'some-head-branch',
|
|
prTitle: 'Some PR',
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 1,
|
|
title: 'Some PR',
|
|
});
|
|
});
|
|
|
|
it('should find pull request with state', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({
|
|
branchName: 'some-head-branch',
|
|
state: 'open',
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 1,
|
|
state: 'open',
|
|
});
|
|
});
|
|
|
|
it('should not find pull request with inverted state', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({
|
|
branchName: 'other-head-branch',
|
|
state: `!open`,
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 2,
|
|
state: 'closed',
|
|
title: 'Other PR',
|
|
});
|
|
});
|
|
|
|
it('should find pull request with title and state', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({
|
|
branchName: 'other-head-branch',
|
|
prTitle: 'Other PR',
|
|
state: 'closed',
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 2,
|
|
state: 'closed',
|
|
title: 'Other PR',
|
|
});
|
|
});
|
|
|
|
it('should find pull request with draft', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({
|
|
branchName: 'draft-head-branch',
|
|
prTitle: 'Draft PR',
|
|
state: 'open',
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 3,
|
|
title: 'Draft PR',
|
|
isDraft: true,
|
|
});
|
|
});
|
|
|
|
it('should find merged pull request', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({ branchName: 'merged-head-branch' });
|
|
|
|
expect(res).toMatchObject({
|
|
number: 4,
|
|
sourceBranch: 'merged-head-branch',
|
|
state: 'merged',
|
|
});
|
|
});
|
|
|
|
it('should return null for missing pull request', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findPr({ branchName: 'missing' });
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('createPr', () => {
|
|
beforeEach(() => {
|
|
memCache.init();
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
});
|
|
|
|
const mockNewPR: MockPr = {
|
|
number: 42,
|
|
state: 'open',
|
|
head: {
|
|
label: 'pr-branch',
|
|
sha: mockCommitHash,
|
|
repo: partial<Repo>({ full_name: mockRepo.full_name }),
|
|
},
|
|
base: {
|
|
ref: mockRepo.default_branch,
|
|
},
|
|
diff_url: 'https://gitea.renovatebot.com/some/repo/pulls/42.diff',
|
|
title: 'pr-title',
|
|
body: 'pr-body',
|
|
mergeable: true,
|
|
created_at: '2014-04-01T05:14:20Z',
|
|
closed_at: '2017-12-28T12:17:48Z',
|
|
updated_at: '2017-12-28T12:17:48Z',
|
|
};
|
|
|
|
it('should use base branch by default', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, {
|
|
...mockNewPR,
|
|
base: { ref: 'devel' },
|
|
});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'devel',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
});
|
|
|
|
it('should use default branch if requested', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
draftPR: true,
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
});
|
|
|
|
it('should resolve and apply optional labels to pull request', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR)
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockRepoLabels)
|
|
.get('/orgs/some/labels')
|
|
.reply(200, mockOrgLabels);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
labels: [...mockRepoLabels, ...mockOrgLabels].map(({ name }) => name),
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
});
|
|
|
|
it('should ensure new pull request gets added to cached pull requests', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await gitea.getPrList();
|
|
await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
});
|
|
const res = await gitea.getPr(mockNewPR.number);
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
});
|
|
|
|
it('should attempt to resolve 409 conflict error (w/o update)', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(409)
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, [mockNewPR]);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
});
|
|
|
|
it('should attempt to resolve 409 conflict error (w/ update)', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(409)
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, [mockNewPR])
|
|
.patch('/repos/some/repo/pulls/42')
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: 'new-title',
|
|
prBody: 'new-body',
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'new-title',
|
|
});
|
|
});
|
|
|
|
it('should abort when response for created pull request is invalid', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, {});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
}),
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
it('should use platform automerge', async () => {
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
const helper = await import('./gitea-helper');
|
|
const mergePR = jest.spyOn(helper, 'mergePR');
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR)
|
|
.post('/repos/some/repo/pulls/42/merge')
|
|
.reply(200);
|
|
await initFakePlatform(scope, '1.17.0');
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
platformPrOptions: { usePlatformAutomerge: true },
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
expect(mergePR).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should use platform automerge on forgejo v7', async () => {
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
const helper = await import('./gitea-helper');
|
|
const mergePR = jest.spyOn(helper, 'mergePR');
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR)
|
|
.post('/repos/some/repo/pulls/42/merge')
|
|
.reply(200);
|
|
await initFakePlatform(scope, '7.0.0-dev-2136-f075579c95+gitea-1.22.0');
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
platformPrOptions: { usePlatformAutomerge: true },
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
expect(mergePR).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should use platform automerge on forgejo v7 LTS', async () => {
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
const helper = await import('./gitea-helper');
|
|
const mergePR = jest.spyOn(helper, 'mergePR');
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR)
|
|
.post('/repos/some/repo/pulls/42/merge')
|
|
.reply(200);
|
|
await initFakePlatform(scope, '7.0.0+LTS-gitea-1.22.0');
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
platformPrOptions: { usePlatformAutomerge: true },
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
expect(mergePR).toHaveBeenCalled();
|
|
});
|
|
|
|
it('continues on platform automerge error', async () => {
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR)
|
|
.post('/repos/some/repo/pulls/42/merge')
|
|
.replyWithError('unknown error');
|
|
await initFakePlatform(scope, '1.17.0');
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
platformPrOptions: { usePlatformAutomerge: true },
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
expect.objectContaining({ prNumber: 42 }),
|
|
'Gitea-native automerge: fail',
|
|
);
|
|
});
|
|
|
|
it('continues if platform automerge is not supported', async () => {
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR);
|
|
await initFakePlatform(scope, '1.10.0');
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
platformPrOptions: { usePlatformAutomerge: true },
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
expect(logger.debug).toHaveBeenCalledWith(
|
|
expect.objectContaining({ prNumber: 42 }),
|
|
'Gitea-native automerge: not supported on this version of Gitea. Use 1.17.0 or newer.',
|
|
);
|
|
});
|
|
|
|
it('should create PR with repository merge method when automergeStrategy is auto', async () => {
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR)
|
|
.post('/repos/some/repo/pulls/42/merge')
|
|
.reply(200);
|
|
await initFakePlatform(scope, '1.17.0');
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
platformPrOptions: {
|
|
automergeStrategy: 'auto',
|
|
usePlatformAutomerge: true,
|
|
},
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
});
|
|
|
|
it.each`
|
|
automergeStrategy | prMergeStrategy
|
|
${'fast-forward'} | ${'rebase'}
|
|
${'merge-commit'} | ${'merge'}
|
|
${'rebase'} | ${'rebase-merge'}
|
|
${'squash'} | ${'squash'}
|
|
`(
|
|
'should create PR with mergeStrategy $prMergeStrategy',
|
|
async ({ automergeStrategy, prMergeStrategy }) => {
|
|
memCache.set('gitea-pr-cache-synced', true);
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls')
|
|
.reply(200, mockNewPR)
|
|
.post('/repos/some/repo/pulls/42/merge')
|
|
.reply(200, {
|
|
Do: prMergeStrategy,
|
|
merge_when_checks_succeed: true,
|
|
});
|
|
await initFakePlatform(scope, '1.17.0');
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.createPr({
|
|
sourceBranch: mockNewPR.head.label,
|
|
targetBranch: 'master',
|
|
prTitle: mockNewPR.title,
|
|
prBody: mockNewPR.body,
|
|
platformPrOptions: {
|
|
automergeStrategy,
|
|
usePlatformAutomerge: true,
|
|
},
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
number: 42,
|
|
title: 'pr-title',
|
|
});
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('updatePr', () => {
|
|
beforeEach(() => {
|
|
memCache.init();
|
|
});
|
|
|
|
it('should update pull request with title', async () => {
|
|
const pr = mockPRs.find((pr) => pr.number === 1);
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs)
|
|
.patch('/repos/some/repo/pulls/1', { title: 'New Title' })
|
|
.reply(200, pr);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.updatePr({ number: 1, prTitle: 'New Title' }),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should update pull target branch', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs)
|
|
.patch('/repos/some/repo/pulls/1', {
|
|
title: 'New Title',
|
|
base: 'New Base',
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.updatePr({
|
|
number: 1,
|
|
prTitle: 'New Title',
|
|
targetBranch: 'New Base',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should update pull request with title and body', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs)
|
|
.patch('/repos/some/repo/pulls/1', {
|
|
title: 'New Title',
|
|
body: 'New Body',
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.updatePr({
|
|
number: 1,
|
|
prTitle: 'New Title',
|
|
prBody: 'New Body',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should update pull request with draft', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs)
|
|
.patch('/repos/some/repo/pulls/3', {
|
|
title: 'WIP: New Title',
|
|
body: 'New Body',
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.updatePr({
|
|
number: 3,
|
|
prTitle: 'New Title',
|
|
prBody: 'New Body',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should close pull request', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs)
|
|
.patch('/repos/some/repo/pulls/1', {
|
|
title: 'New Title',
|
|
body: 'New Body',
|
|
state: 'closed',
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.updatePr({
|
|
number: 1,
|
|
prTitle: 'New Title',
|
|
prBody: 'New Body',
|
|
state: 'closed',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should update labels', async () => {
|
|
const updatedMockPR = partial<PR>({
|
|
...mockPRs[0],
|
|
number: 1,
|
|
title: 'New Title',
|
|
body: 'New Body',
|
|
state: 'open',
|
|
labels: [
|
|
{
|
|
id: 1,
|
|
name: 'some-label',
|
|
},
|
|
],
|
|
});
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs)
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockRepoLabels)
|
|
.get('/orgs/some/labels')
|
|
.reply(200, mockOrgLabels)
|
|
.patch('/repos/some/repo/pulls/1')
|
|
.reply(200, updatedMockPR);
|
|
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
await expect(
|
|
gitea.updatePr({
|
|
number: 1,
|
|
prTitle: 'New Title',
|
|
prBody: 'New Body',
|
|
state: 'open',
|
|
labels: ['some-label'],
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should log a warning if labels could not be looked up', async () => {
|
|
const updatedMockPR = partial<PR>({
|
|
...mockPRs[0],
|
|
number: 1,
|
|
title: 'New Title',
|
|
body: 'New Body',
|
|
state: 'open',
|
|
labels: [
|
|
{
|
|
id: 1,
|
|
name: 'some-label',
|
|
},
|
|
],
|
|
});
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs)
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockRepoLabels)
|
|
.get('/orgs/some/labels')
|
|
.reply(200, mockOrgLabels)
|
|
.patch('/repos/some/repo/pulls/1')
|
|
.reply(200, updatedMockPR);
|
|
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
await expect(
|
|
gitea.updatePr({
|
|
number: 1,
|
|
prTitle: 'New Title',
|
|
prBody: 'New Body',
|
|
state: 'open',
|
|
labels: ['some-label', 'unavailable-label'],
|
|
}),
|
|
).toResolve();
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
'Some labels could not be looked up. Renovate may halt label updates assuming changes by others.',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('mergePr', () => {
|
|
it('should return true when merging succeeds', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls/1/merge', {
|
|
Do: 'rebase',
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.mergePr({
|
|
branchName: 'some-branch',
|
|
id: 1,
|
|
});
|
|
|
|
expect(res).toBe(true);
|
|
});
|
|
|
|
it('should return false when merging fails', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls/1/merge', {
|
|
Do: 'squash',
|
|
})
|
|
.replyWithError('unknown');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.mergePr({
|
|
branchName: 'some-branch',
|
|
id: 1,
|
|
strategy: 'squash',
|
|
});
|
|
|
|
expect(res).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getIssueList', () => {
|
|
it('should return empty for disabled issues', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope, { has_issues: false });
|
|
|
|
const res = await gitea.getIssueList();
|
|
|
|
expect(res).toBeEmptyArray();
|
|
});
|
|
});
|
|
|
|
describe('getIssue', () => {
|
|
it('should return the issue', async () => {
|
|
const mockIssue = mockIssues.find((i) => i.number === 1)!;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1')
|
|
.reply(200, mockIssue);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getIssue?.(mockIssue.number);
|
|
|
|
expect(res).toEqual({
|
|
body: 'some-content',
|
|
number: 1,
|
|
});
|
|
});
|
|
|
|
it('should return null for disabled issues', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope, { has_issues: false });
|
|
|
|
const res = await gitea.getIssue!(1);
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('findIssue', () => {
|
|
it('should return existing open issue', async () => {
|
|
const mockIssue = mockIssues.find(({ title }) => title === 'open-issue')!;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues)
|
|
.get('/repos/some/repo/issues/1')
|
|
.reply(200, mockIssue);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findIssue(mockIssue.title);
|
|
|
|
expect(res).toMatchObject({
|
|
body: 'some-content',
|
|
number: 1,
|
|
});
|
|
});
|
|
|
|
it('should not return existing closed issue', async () => {
|
|
const mockIssue = mockIssues.find(
|
|
({ title }) => title === 'closed-issue',
|
|
)!;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findIssue(mockIssue.title);
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('should return null for missing issue', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.findIssue('missing');
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('ensureIssue', () => {
|
|
it('should create issue if not found', async () => {
|
|
const mockIssue = {
|
|
title: 'new-title',
|
|
body: 'new-body',
|
|
shouldReOpen: false,
|
|
once: false,
|
|
};
|
|
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues)
|
|
.post('/repos/some/repo/issues', {
|
|
body: mockIssue.body,
|
|
title: mockIssue.title,
|
|
})
|
|
.reply(200, { number: 42 });
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue(mockIssue);
|
|
|
|
expect(res).toBe('created');
|
|
});
|
|
|
|
it('should create issue with the correct labels', async () => {
|
|
const mockIssue: EnsureIssueConfig = {
|
|
title: 'new-title',
|
|
body: 'new-body',
|
|
shouldReOpen: false,
|
|
once: false,
|
|
labels: ['Renovate', 'Maintenance'],
|
|
};
|
|
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues)
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, [
|
|
partial<Label>({ id: 1, name: 'Renovate' }),
|
|
partial<Label>({ id: 3, name: 'Maintenance' }),
|
|
] satisfies Label[])
|
|
.get('/orgs/some/labels')
|
|
.reply(200, mockOrgLabels)
|
|
.post('/repos/some/repo/issues', {
|
|
body: 'new-body',
|
|
title: 'new-title',
|
|
labels: [1, 3],
|
|
})
|
|
.reply(200, { number: 42 });
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue(mockIssue);
|
|
|
|
expect(res).toBe('created');
|
|
});
|
|
|
|
it('should not reopen closed issue by default', async () => {
|
|
const closedIssue = mockIssues.find((i) => i.title === 'closed-issue')!;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues)
|
|
.patch('/repos/some/repo/issues/2', {
|
|
body: closedIssue.body,
|
|
state: closedIssue.state,
|
|
title: 'closed-issue',
|
|
})
|
|
.reply(200, closedIssue);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue({
|
|
title: closedIssue.title,
|
|
body: closedIssue.body,
|
|
shouldReOpen: false,
|
|
once: false,
|
|
});
|
|
|
|
expect(res).toBe('updated');
|
|
});
|
|
|
|
it('should not update labels when not necessary', async () => {
|
|
const mockLabels: Label[] = [
|
|
partial<Label>({ id: 1, name: 'Renovate' }),
|
|
partial<Label>({ id: 3, name: 'Maintenance' }),
|
|
];
|
|
const mockIssue: Issue = {
|
|
number: 10,
|
|
title: 'label-issue',
|
|
body: 'label-body',
|
|
assignees: [],
|
|
labels: mockLabels,
|
|
state: 'open',
|
|
};
|
|
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, [mockIssue])
|
|
.patch('/repos/some/repo/issues/10')
|
|
.reply(200, mockIssue)
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockLabels)
|
|
.get('/orgs/some/labels')
|
|
.reply(200, []);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue({
|
|
title: mockIssue.title,
|
|
body: 'new-body',
|
|
labels: ['Renovate', 'Maintenance'],
|
|
});
|
|
|
|
expect(res).toBe('updated');
|
|
});
|
|
|
|
it('should update labels when missing', async () => {
|
|
const mockLabels: Label[] = [
|
|
partial<Label>({ id: 1, name: 'Renovate' }),
|
|
partial<Label>({ id: 3, name: 'Maintenance' }),
|
|
];
|
|
const mockIssue: Issue = {
|
|
number: 10,
|
|
title: 'label-issue',
|
|
body: 'label-body',
|
|
assignees: [],
|
|
labels: [mockLabels[0]],
|
|
state: 'open',
|
|
};
|
|
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, [mockIssue])
|
|
.patch('/repos/some/repo/issues/10')
|
|
.reply(200, mockIssue)
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockLabels)
|
|
.get('/orgs/some/labels')
|
|
.reply(200, [])
|
|
.put('/repos/some/repo/issues/10/labels', { labels: [1, 3] })
|
|
.reply(200, mockLabels);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue({
|
|
title: mockIssue.title,
|
|
body: 'new-body',
|
|
labels: ['Renovate', 'Maintenance'],
|
|
});
|
|
|
|
expect(res).toBe('updated');
|
|
});
|
|
|
|
it('should reset labels when others have been set', async () => {
|
|
const mockLabels: Label[] = [
|
|
partial<Label>({ id: 1, name: 'Renovate' }),
|
|
partial<Label>({ id: 2, name: 'Other label' }),
|
|
partial<Label>({ id: 3, name: 'Maintenance' }),
|
|
];
|
|
const mockIssue: Issue = {
|
|
number: 10,
|
|
title: 'label-issue',
|
|
body: 'label-body',
|
|
assignees: [],
|
|
labels: mockLabels,
|
|
state: 'open',
|
|
};
|
|
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, [mockIssue])
|
|
.patch('/repos/some/repo/issues/10')
|
|
.reply(200, mockIssue)
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockLabels)
|
|
.get('/orgs/some/labels')
|
|
.reply(200, [])
|
|
.put('/repos/some/repo/issues/10/labels', { labels: [1, 3] })
|
|
.reply(200, mockLabels);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue({
|
|
title: mockIssue.title,
|
|
body: 'new-body',
|
|
labels: ['Renovate', 'Maintenance'],
|
|
});
|
|
|
|
expect(res).toBe('updated');
|
|
});
|
|
|
|
it('should reopen closed issue if desired', async () => {
|
|
const closedIssue = mockIssues.find((i) => i.title === 'closed-issue')!;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues)
|
|
.patch('/repos/some/repo/issues/2', {
|
|
body: closedIssue.body,
|
|
state: 'open',
|
|
title: 'closed-issue',
|
|
})
|
|
.reply(200, { ...closedIssue, state: 'open' });
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue({
|
|
title: closedIssue.title,
|
|
body: closedIssue.body,
|
|
shouldReOpen: true,
|
|
once: false,
|
|
});
|
|
|
|
expect(res).toBe('updated');
|
|
});
|
|
|
|
it('should not update existing closed issue if desired', async () => {
|
|
const closedIssue = mockIssues.find((i) => i.title === 'closed-issue')!;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue({
|
|
title: closedIssue.title,
|
|
body: closedIssue.body,
|
|
shouldReOpen: false,
|
|
once: true,
|
|
});
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('should close all open duplicate issues except first one when updating', async () => {
|
|
const duplicates = mockIssues.filter(
|
|
(i) => i.title === 'duplicate-issue',
|
|
);
|
|
const [first, second, third] = duplicates;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, duplicates)
|
|
.patch(`/repos/some/repo/issues/${second.number}`, {
|
|
state: 'closed',
|
|
})
|
|
.reply(200, { ...second, state: 'closed' })
|
|
.patch(`/repos/some/repo/issues/${third.number}`, {
|
|
state: 'closed',
|
|
})
|
|
.reply(200, { ...third, state: 'closed' });
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureIssue({
|
|
title: first.title,
|
|
body: first.body,
|
|
shouldReOpen: false,
|
|
once: false,
|
|
});
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('should reset issue cache when creating an issue', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.twice()
|
|
.reply(200, mockIssues)
|
|
.post('/repos/some/repo/issues')
|
|
.reply(200, { number: 42 });
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.ensureIssue({
|
|
title: 'new-title',
|
|
body: 'new-body',
|
|
shouldReOpen: false,
|
|
once: false,
|
|
}),
|
|
).resolves.toBe('created');
|
|
|
|
await expect(gitea.getIssueList()).toResolve();
|
|
});
|
|
|
|
it('should gracefully fail with warning', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.replyWithError('unknown');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await gitea.ensureIssue({
|
|
title: 'new-title',
|
|
body: 'new-body',
|
|
shouldReOpen: false,
|
|
once: false,
|
|
});
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
{ err: expect.any(Error) },
|
|
'Could not ensure issue',
|
|
);
|
|
});
|
|
|
|
it('should return null for disabled issues', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope, { has_issues: false });
|
|
|
|
await expect(
|
|
gitea.ensureIssue({
|
|
title: 'new-title',
|
|
body: 'new-body',
|
|
shouldReOpen: false,
|
|
once: false,
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('ensureIssueClosing', () => {
|
|
it('should close issues with matching title', async () => {
|
|
const mockIssue = mockIssues[0];
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues')
|
|
.query({ state: 'all', type: 'issues' })
|
|
.reply(200, mockIssues)
|
|
.patch('/repos/some/repo/issues/1', { state: 'closed' })
|
|
.reply(200, { ...mockIssue, state: 'closed' });
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.ensureIssueClosing(mockIssue.title)).toResolve();
|
|
});
|
|
|
|
it('should return for disabled issues', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope, { has_issues: false });
|
|
await expect(gitea.ensureIssueClosing('new-title')).toResolve();
|
|
});
|
|
});
|
|
|
|
describe('deleteLabel', () => {
|
|
it('should delete a label which exists', async () => {
|
|
const mockLabel = mockRepoLabels[0];
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/orgs/some/labels')
|
|
.replyWithError('unknown')
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockRepoLabels)
|
|
.delete(`/repos/some/repo/issues/42/labels/${mockLabel.id}`)
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.deleteLabel(42, mockLabel.name)).toResolve();
|
|
});
|
|
|
|
it('should gracefully fail with warning if label is missing', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/orgs/some/labels')
|
|
.reply(200, [])
|
|
.get('/repos/some/repo/labels')
|
|
.reply(200, mockRepoLabels);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.deleteLabel(42, 'missing')).toResolve();
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
{ issue: 42, labelName: 'missing' },
|
|
'Failed to lookup label for deletion',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('ensureComment', () => {
|
|
it('should add comment with topic if not found', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments)
|
|
.post('/repos/some/repo/issues/1/comments', {
|
|
body: '### other-topic\n\nother-content',
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureComment({
|
|
number: 1,
|
|
topic: 'other-topic',
|
|
content: 'other-content',
|
|
});
|
|
|
|
expect(res).toBeTrue();
|
|
});
|
|
|
|
it('should add comment without topic if not found', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments)
|
|
.post('/repos/some/repo/issues/1/comments', { body: 'other-content' })
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureComment({
|
|
number: 1,
|
|
content: 'other-content',
|
|
topic: null,
|
|
});
|
|
|
|
expect(res).toBeTrue();
|
|
});
|
|
|
|
it('should update comment with topic if found', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments)
|
|
.patch('/repos/some/repo/issues/comments/13', {
|
|
body: '### some-topic\n\nsome-new-content',
|
|
})
|
|
.reply(200, partial<Comment>({ id: 13 }));
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureComment({
|
|
number: 1,
|
|
topic: 'some-topic',
|
|
content: 'some-new-content',
|
|
});
|
|
|
|
expect(res).toBeTrue();
|
|
});
|
|
|
|
it('should skip if comment is up-to-date', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureComment({
|
|
number: 1,
|
|
topic: 'some-topic',
|
|
content: 'some-content',
|
|
});
|
|
|
|
expect(res).toBeTrue();
|
|
});
|
|
|
|
it('should gracefully fail with warning', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.replyWithError('unknown');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.ensureComment({
|
|
number: 1,
|
|
topic: 'some-topic',
|
|
content: 'some-content',
|
|
});
|
|
|
|
expect(res).toBe(false);
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
{ err: expect.any(Error), issue: 1, subject: 'some-topic' },
|
|
'Error ensuring comment',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('ensureCommentRemoval', () => {
|
|
it('should remove existing comment by topic', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments)
|
|
.delete('/repos/some/repo/issues/comments/13')
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.ensureCommentRemoval({
|
|
type: 'by-topic',
|
|
number: 1,
|
|
topic: 'some-topic',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should remove existing comment by content', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments)
|
|
.delete('/repos/some/repo/issues/comments/11')
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.ensureCommentRemoval({
|
|
type: 'by-content',
|
|
number: 1,
|
|
content: 'some-body',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
|
|
it('should gracefully fail with warning', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments)
|
|
.delete('/repos/some/repo/issues/comments/13')
|
|
.replyWithError('unknown');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await gitea.ensureCommentRemoval({
|
|
type: 'by-topic',
|
|
number: 1,
|
|
topic: 'some-topic',
|
|
});
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
{
|
|
config: { number: 1, topic: 'some-topic', type: 'by-topic' },
|
|
err: expect.any(Error),
|
|
issue: 1,
|
|
},
|
|
'Error deleting comment',
|
|
);
|
|
});
|
|
|
|
it('should abort silently if comment is missing', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/issues/1/comments')
|
|
.reply(200, mockComments);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(
|
|
gitea.ensureCommentRemoval({
|
|
type: 'by-topic',
|
|
number: 1,
|
|
topic: 'missing',
|
|
}),
|
|
).toResolve();
|
|
});
|
|
});
|
|
|
|
describe('getBranchPr', () => {
|
|
beforeEach(() => {
|
|
memCache.init();
|
|
});
|
|
|
|
it('should return existing pull request for branch', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getBranchPr('some-head-branch');
|
|
|
|
expect(res).toMatchObject({ number: 1 });
|
|
});
|
|
|
|
it('should return null if no pull request exists', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/pulls')
|
|
.query({ state: 'all', sort: 'recentupdate' })
|
|
.reply(200, mockPRs);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
expect(await gitea.getBranchPr('missing')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('addAssignees', () => {
|
|
it('should add assignees to the issue', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.patch('/repos/some/repo/issues/1', {
|
|
assignees: ['me', 'you'],
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.addAssignees(1, ['me', 'you'])).toResolve();
|
|
});
|
|
});
|
|
|
|
describe('addReviewers', () => {
|
|
it('should assign reviewers', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls/1/requested_reviewers', {
|
|
reviewers: ['me', 'you'],
|
|
})
|
|
.reply(200);
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.addReviewers(1, ['me', 'you'])).toResolve();
|
|
|
|
expect(logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should do nothing for older Gitea versions', async () => {
|
|
const scope = httpMock.scope('https://gitea.com/api/v1');
|
|
await initFakePlatform(scope, '1.10.0');
|
|
await initFakeRepo(scope);
|
|
|
|
await expect(gitea.addReviewers(1, ['me', 'you'])).toResolve();
|
|
});
|
|
|
|
it('catches errors', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.post('/repos/some/repo/pulls/1/requested_reviewers', {
|
|
reviewers: ['me', 'you'],
|
|
})
|
|
.replyWithError('unknown');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
///
|
|
await expect(gitea.addReviewers(1, ['me', 'you'])).toResolve();
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
{ err: expect.any(Error), number: 1, reviewers: ['me', 'you'] },
|
|
'Failed to assign reviewer',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('massageMarkdown', () => {
|
|
it('replaces pr links', () => {
|
|
const body =
|
|
'[#123](../pull/123) [#124](../pull/124) [#125](../pull/125)';
|
|
|
|
expect(gitea.massageMarkdown(body)).toBe(
|
|
'[#123](pulls/123) [#124](pulls/124) [#125](pulls/125)',
|
|
);
|
|
});
|
|
});
|
|
|
|
it('maxBodyLength', () => {
|
|
expect(gitea.maxBodyLength()).toBe(1000000);
|
|
});
|
|
|
|
describe('getJsonFile()', () => {
|
|
it('returns file content', async () => {
|
|
const data = { foo: 'bar' };
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/contents/file.json')
|
|
.reply(200, {
|
|
content: Buffer.from(JSON.stringify(data), 'utf-8'),
|
|
});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getJsonFile('file.json');
|
|
|
|
expect(res).toEqual(data);
|
|
});
|
|
|
|
it('returns file content from given repo', async () => {
|
|
const data = { foo: 'bar' };
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/different/repo/contents/file.json')
|
|
.reply(200, {
|
|
content: Buffer.from(JSON.stringify(data), 'utf-8'),
|
|
});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope, { full_name: 'different/repo' });
|
|
|
|
const res = await gitea.getJsonFile('file.json', 'different/repo');
|
|
|
|
expect(res).toEqual(data);
|
|
});
|
|
|
|
it('returns file content from branch or tag', async () => {
|
|
const data = { foo: 'bar' };
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/contents/file.json?ref=dev')
|
|
.reply(200, {
|
|
content: Buffer.from(JSON.stringify(data), 'utf-8'),
|
|
});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getJsonFile('file.json', 'some/repo', 'dev');
|
|
|
|
expect(res).toEqual(data);
|
|
});
|
|
|
|
it('returns file content in json5 format', async () => {
|
|
const json5Data = `
|
|
{
|
|
// json5 comment
|
|
foo: 'bar'
|
|
}
|
|
`;
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/contents/file.json5')
|
|
.reply(200, {
|
|
content: Buffer.from(json5Data, 'utf-8'),
|
|
});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
|
|
const res = await gitea.getJsonFile('file.json5');
|
|
|
|
expect(res).toEqual({ foo: 'bar' });
|
|
});
|
|
|
|
it('throws on malformed JSON', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/contents/file.json')
|
|
.reply(200, {
|
|
content: Buffer.from('!@#', 'utf-8'),
|
|
});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
await expect(gitea.getJsonFile('file.json')).rejects.toThrow();
|
|
});
|
|
|
|
it('returns null on missing content', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/contents/file.json')
|
|
.reply(200, {});
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
expect(await gitea.getJsonFile('file.json')).toBeNull();
|
|
});
|
|
|
|
it('throws on errors', async () => {
|
|
const scope = httpMock
|
|
.scope('https://gitea.com/api/v1')
|
|
.get('/repos/some/repo/contents/file.json')
|
|
.replyWithError('unknown');
|
|
await initFakePlatform(scope);
|
|
await initFakeRepo(scope);
|
|
await expect(gitea.getJsonFile('file.json')).rejects.toThrow();
|
|
});
|
|
});
|
|
});
|