Files
labeler/__tests__/add-labels.test.ts
T
b7a8804475 Preserve externally managed labels during synchronization (#957)
* Preserve externally managed pull request labels

Replace whole-set label writes with batched selective additions and removals so sync-labels only manages configured labels.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 44452010-1cd5-4e90-8abe-f03547c04182

* Reconcile ambiguous label addition failures

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 44452010-1cd5-4e90-8abe-f03547c04182

* Handle paginated label reconciliation

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 44452010-1cd5-4e90-8abe-f03547c04182

---------

Copilot-Session: 44452010-1cd5-4e90-8abe-f03547c04182
2026-07-30 21:51:30 -05:00

132 lines
4.2 KiB
TypeScript

import {jest, describe, it, expect} from '@jest/globals';
import type {ClientType} from '../src/api/types.js';
jest.unstable_mockModule('@actions/github', () => ({
context: {
repo: {owner: 'monalisa', repo: 'helloworld'}
}
}));
const {addLabels} = await import('../src/api/add-labels.js');
const createClient = () => {
const addLabelsMock = jest.fn<any>();
const listLabelsOnIssueMock = jest.fn<any>();
const client = {
rest: {
issues: {
addLabels: addLabelsMock,
listLabelsOnIssue: listLabelsOnIssueMock
}
}
} as ClientType;
return {client, addLabelsMock, listLabelsOnIssueMock};
};
describe('addLabels', () => {
it('does not verify a successful addition', async () => {
const {client, addLabelsMock, listLabelsOnIssueMock} = createClient();
addLabelsMock.mockResolvedValue({data: []});
await addLabels(client, 123, ['bug']);
expect(addLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['bug'],
request: {retries: 0}
});
expect(listLabelsOnIssueMock).not.toHaveBeenCalled();
});
it('accepts a server error when every requested label was committed', async () => {
const {client, addLabelsMock, listLabelsOnIssueMock} = createClient();
const serverError = Object.assign(new Error('Bad Gateway'), {status: 502});
addLabelsMock.mockRejectedValue(serverError);
listLabelsOnIssueMock.mockResolvedValue({
data: [{name: 'BUG'}, {name: 'documentation'}],
headers: {}
});
await expect(
addLabels(client, 123, ['bug', 'documentation'])
).resolves.toBeUndefined();
expect(listLabelsOnIssueMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
per_page: 100,
page: 1,
request: {retries: 0}
});
});
it('checks subsequent pages after a committed server error', async () => {
const {client, addLabelsMock, listLabelsOnIssueMock} = createClient();
const serverError = Object.assign(new Error('Bad Gateway'), {status: 502});
addLabelsMock.mockRejectedValue(serverError);
listLabelsOnIssueMock
.mockResolvedValueOnce({
data: Array.from({length: 100}, (_, index) => ({
name: `label-${index}`
})),
headers: {
link: '<https://api.github.com/issues/123/labels?page=2>; rel="next"'
}
})
.mockResolvedValueOnce({
data: [{name: 'documentation'}],
headers: {}
});
await expect(
addLabels(client, 123, ['label-0', 'documentation'])
).resolves.toBeUndefined();
expect(listLabelsOnIssueMock).toHaveBeenNthCalledWith(2, {
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
per_page: 100,
page: 2,
request: {retries: 0}
});
});
it('preserves a server error when any requested label is missing', async () => {
const {client, addLabelsMock, listLabelsOnIssueMock} = createClient();
const serverError = Object.assign(new Error('Bad Gateway'), {status: 502});
addLabelsMock.mockRejectedValue(serverError);
listLabelsOnIssueMock.mockResolvedValue({
data: [{name: 'bug'}],
headers: {}
});
await expect(addLabels(client, 123, ['bug', 'documentation'])).rejects.toBe(
serverError
);
expect(listLabelsOnIssueMock).toHaveBeenCalledTimes(1);
});
it('does not verify non-server errors', async () => {
const {client, addLabelsMock, listLabelsOnIssueMock} = createClient();
const validationError = Object.assign(new Error('Validation Failed'), {
status: 422
});
addLabelsMock.mockRejectedValue(validationError);
await expect(addLabels(client, 123, ['bug'])).rejects.toBe(validationError);
expect(listLabelsOnIssueMock).not.toHaveBeenCalled();
});
it('preserves the server error when verification fails', async () => {
const {client, addLabelsMock, listLabelsOnIssueMock} = createClient();
const serverError = Object.assign(new Error('Bad Gateway'), {status: 502});
addLabelsMock.mockRejectedValue(serverError);
listLabelsOnIssueMock.mockRejectedValue(new Error('Service unavailable'));
await expect(addLabels(client, 123, ['bug'])).rejects.toBe(serverError);
});
});