fix(gerrit): not auto-approving if change already had a Code-Review +1

This commit is contained in:
Felipe Santos 2024-11-29 19:42:54 -03:00
parent 5459f59030
commit 46f8eed01f
5 changed files with 103 additions and 28 deletions

View file

@ -393,10 +393,20 @@ describe('modules/platform/gerrit/client', () => {
describe('approveChange()', () => { describe('approveChange()', () => {
it('already approved - do nothing', async () => { it('already approved - do nothing', async () => {
const change = partial<GerritChange>({}); const change = partial<GerritChange>({
labels: {
'Code-Review': {
all: [{ value: 2 }],
},
},
});
httpMock httpMock
.scope(gerritEndpointUrl) .scope(gerritEndpointUrl)
.get((url) => url.includes('/a/changes/123456?o=')) .get(
(url) =>
url.includes('/a/changes/123456?o=') &&
url.includes('o=DETAILED_LABELS'),
)
.reply(200, gerritRestResponse(change), jsonResultHeader); .reply(200, gerritRestResponse(change), jsonResultHeader);
await expect(client.approveChange(123456)).toResolve(); await expect(client.approveChange(123456)).toResolve();
}); });
@ -405,17 +415,53 @@ describe('modules/platform/gerrit/client', () => {
const change = partial<GerritChange>({ labels: {} }); const change = partial<GerritChange>({ labels: {} });
httpMock httpMock
.scope(gerritEndpointUrl) .scope(gerritEndpointUrl)
.get((url) => url.includes('/a/changes/123456?o=')) .get(
(url) =>
url.includes('/a/changes/123456?o=') &&
url.includes('o=DETAILED_LABELS'),
)
.reply(200, gerritRestResponse(change), jsonResultHeader); .reply(200, gerritRestResponse(change), jsonResultHeader);
await expect(client.approveChange(123456)).toResolve(); await expect(client.approveChange(123456)).toResolve();
}); });
it('not already approved - approve now', async () => { it('not already approved - approve now', async () => {
const change = partial<GerritChange>({ labels: { 'Code-Review': {} } }); const change = partial<GerritChange>({
labels: { 'Code-Review': { all: [] } },
});
httpMock httpMock
.scope(gerritEndpointUrl) .scope(gerritEndpointUrl)
.get((url) => url.includes('/a/changes/123456?o=')) .get(
(url) =>
url.includes('/a/changes/123456?o=') &&
url.includes('o=DETAILED_LABELS'),
)
.reply(200, gerritRestResponse(change), jsonResultHeader);
const approveMock = httpMock
.scope(gerritEndpointUrl)
.post('/a/changes/123456/revisions/current/review', {
labels: { 'Code-Review': +2 },
})
.reply(200, gerritRestResponse(''), jsonResultHeader);
await expect(client.approveChange(123456)).toResolve();
expect(approveMock.isDone()).toBeTrue();
});
it('not already approved because of +1 - approve now', async () => {
const change = partial<GerritChange>({
labels: {
'Code-Review': {
all: [{ value: 1 }],
},
},
});
httpMock
.scope(gerritEndpointUrl)
.get(
(url) =>
url.includes('/a/changes/123456?o=') &&
url.includes('o=DETAILED_LABELS'),
)
.reply(200, gerritRestResponse(change), jsonResultHeader); .reply(200, gerritRestResponse(change), jsonResultHeader);
const approveMock = httpMock const approveMock = httpMock
.scope(gerritEndpointUrl) .scope(gerritEndpointUrl)
@ -432,7 +478,7 @@ describe('modules/platform/gerrit/client', () => {
it('label not exists', () => { it('label not exists', () => {
expect( expect(
client.wasApprovedBy(partial<GerritChange>({}), 'user'), client.wasApprovedBy(partial<GerritChange>({}), 'user'),
).toBeUndefined(); ).toBeFalse();
}); });
it('not approved by anyone', () => { it('not approved by anyone', () => {
@ -440,12 +486,29 @@ describe('modules/platform/gerrit/client', () => {
client.wasApprovedBy( client.wasApprovedBy(
partial<GerritChange>({ partial<GerritChange>({
labels: { labels: {
'Code-Review': {}, 'Code-Review': {
all: [],
},
}, },
}), }),
'user', 'user',
), ),
).toBeUndefined(); ).toBeFalse();
});
it('not approved but with +1', () => {
expect(
client.wasApprovedBy(
partial<GerritChange>({
labels: {
'Code-Review': {
all: [{ value: 1, username: 'user' }],
},
},
}),
'user',
),
).toBeFalse();
}); });
it('approved by given user', () => { it('approved by given user', () => {
@ -454,10 +517,7 @@ describe('modules/platform/gerrit/client', () => {
partial<GerritChange>({ partial<GerritChange>({
labels: { labels: {
'Code-Review': { 'Code-Review': {
approved: { all: [{ value: 2, username: 'user' }],
_account_id: 1,
username: 'user',
},
}, },
}, },
}), }),
@ -472,10 +532,7 @@ describe('modules/platform/gerrit/client', () => {
partial<GerritChange>({ partial<GerritChange>({
labels: { labels: {
'Code-Review': { 'Code-Review': {
approved: { all: [{ value: 2, username: 'other' }],
_account_id: 1,
username: 'other',
},
}, },
}, },
}), }),

View file

@ -58,12 +58,14 @@ class GerritClient {
repository: string, repository: string,
findPRConfig: GerritFindPRConfig, findPRConfig: GerritFindPRConfig,
refreshCache?: boolean, refreshCache?: boolean,
extraOptions?: string[],
): Promise<GerritChange[]> { ): Promise<GerritChange[]> {
const filters = GerritClient.buildSearchFilters(repository, findPRConfig); const filters = GerritClient.buildSearchFilters(repository, findPRConfig);
const options = [...this.requestDetails, ...(extraOptions ?? [])];
const changes = await this.gerritHttp.getJson<GerritChange[]>( const changes = await this.gerritHttp.getJson<GerritChange[]>(
`a/changes/?q=` + `a/changes/?q=` +
filters.join('+') + filters.join('+') +
this.requestDetails.map((det) => `&o=${det}`).join(''), options.map((det) => `&o=${det}`).join(''),
{ memCache: !refreshCache }, { memCache: !refreshCache },
); );
logger.trace( logger.trace(
@ -72,10 +74,13 @@ class GerritClient {
return changes.body; return changes.body;
} }
async getChange(changeNumber: number): Promise<GerritChange> { async getChange(
changeNumber: number,
extraOptions?: string[],
): Promise<GerritChange> {
const options = [...this.requestDetails, ...(extraOptions ?? [])];
const changes = await this.gerritHttp.getJson<GerritChange>( const changes = await this.gerritHttp.getJson<GerritChange>(
`a/changes/${changeNumber}?` + `a/changes/${changeNumber}?` + options.map((det) => `o=${det}`).join('&'),
this.requestDetails.map((det) => `o=${det}`).join('&'),
); );
return changes.body; return changes.body;
} }
@ -189,15 +194,20 @@ class GerritClient {
} }
async checkIfApproved(changeId: number): Promise<boolean> { async checkIfApproved(changeId: number): Promise<boolean> {
const change = await client.getChange(changeId); const change = await client.getChange(changeId, ['DETAILED_LABELS']);
const reviewLabels = change?.labels?.['Code-Review']; const reviewLabel = change?.labels?.['Code-Review'];
return reviewLabels === undefined || reviewLabels.approved !== undefined; return (
reviewLabel === undefined ||
Boolean(reviewLabel.all?.some((label) => label.value === 2))
);
} }
wasApprovedBy(change: GerritChange, username: string): boolean | undefined { wasApprovedBy(change: GerritChange, username: string): boolean {
return ( const reviewLabel = change?.labels?.['Code-Review'];
change.labels?.['Code-Review'].approved && return Boolean(
change.labels['Code-Review'].approved.username === username reviewLabel?.all?.some(
(label) => label.value === 2 && label.username === username,
),
); );
} }

View file

@ -277,6 +277,7 @@ describe('modules/platform/gerrit/scm', () => {
targetBranch: 'main', targetBranch: 'main',
}, },
true, true,
['DETAILED_LABELS'],
); );
}); });

View file

@ -101,7 +101,7 @@ export class GerritScm extends DefaultGitScm {
targetBranch: commit.baseBranch, targetBranch: commit.baseBranch,
}; };
const existingChange = await client const existingChange = await client
.findChanges(repository, searchConfig, true) .findChanges(repository, searchConfig, true, ['DETAILED_LABELS'])
.then((res) => res.pop()); .then((res) => res.pop());
let hasChanges = true; let hasChanges = true;

View file

@ -77,6 +77,8 @@ export interface GerritChangeMessageInfo {
export interface GerritLabelInfo { export interface GerritLabelInfo {
approved?: GerritAccountInfo; approved?: GerritAccountInfo;
rejected?: GerritAccountInfo; rejected?: GerritAccountInfo;
/** List of votes. Only set when o=DETAILED_LABELS. */
all?: GerritApprovalInfo[];
} }
export interface GerritActionInfo { export interface GerritActionInfo {
@ -99,3 +101,8 @@ export interface GerritMergeableInfo {
| 'CHERRY_PICK'; | 'CHERRY_PICK';
mergeable: boolean; mergeable: boolean;
} }
export interface GerritApprovalInfo {
value?: number;
username?: string;
}