diff --git a/src/infrastructure/KyRequester.ts b/src/infrastructure/KyRequester.ts index d49c9e42..93ab50c5 100644 --- a/src/infrastructure/KyRequester.ts +++ b/src/infrastructure/KyRequester.ts @@ -69,7 +69,7 @@ methods.forEach(m => { e.description = output.error || output.message; } - + throw e; } diff --git a/src/infrastructure/RequestHelper.ts b/src/infrastructure/RequestHelper.ts index bc640f89..5e7c9e04 100644 --- a/src/infrastructure/RequestHelper.ts +++ b/src/infrastructure/RequestHelper.ts @@ -21,15 +21,16 @@ export async function get( query: query || {}, sudo, }); + const { headers } = response; let { body } = response; let pagination = { - total: headers['x-total'], + total: parseInt(headers['x-total'], 10), next: parseInt(headers['x-next-page'], 10) || null, current: parseInt(headers['x-page'], 10) || 1, - previous: headers['x-prev-page'] || null, - perPage: headers['x-per-page'], - totalPages: headers['x-total-pages'], + previous: parseInt(headers['x-prev-page'], 10) || null, + perPage: parseInt(headers['x-per-page'], 10), + totalPages: parseInt(headers['x-total-pages'], 10), }; const underLimit = maxPages ? pagination.current < maxPages : true; diff --git a/src/services/Labels.ts b/src/services/Labels.ts index 269396de..7324c9d8 100644 --- a/src/services/Labels.ts +++ b/src/services/Labels.ts @@ -14,10 +14,10 @@ class Labels extends BaseService { return RequestHelper.get(this, `projects/${pId}/labels`, options); } - create(projectId: ProjectId, options?: BaseRequestOptions) { + create(projectId: ProjectId, labelName: string, color: string, options?: BaseRequestOptions) { const pId = encodeURIComponent(projectId); - return RequestHelper.post(this, `projects/${pId}/labels`, options); + return RequestHelper.post(this, `projects/${pId}/labels`, { name: labelName, color, ...options }); } edit(projectId: ProjectId, labelName: string, options?: BaseRequestOptions) { diff --git a/test/integration/services/Issues.ts b/test/integration/services/Issues.ts index 4a4f5045..9033937d 100644 --- a/test/integration/services/Issues.ts +++ b/test/integration/services/Issues.ts @@ -49,20 +49,20 @@ describe('Issues.all', () => { const issues = await service.all(); expect(issues).toBeInstanceOf(Array); - expect(issues.length).toEqual(2); + expect(issues).toHaveLength(2); }); it('should return a list filtered to a specfic page', async () => { const issues1 = await service.all({projectId: project.id, perPage: 1, page: 1 }); expect(issues1).toBeInstanceOf(Array); - expect(issues1.length).toEqual(1); + expect(issues1).toHaveLength(1); expect(issues1[0].title).toBe('Issue Integration test2'); const issues2 = await service.all({ projectId: project.id, perPage: 1, page: 2 }); expect(issues2).toBeInstanceOf(Array); - expect(issues2.length).toEqual(1); + expect(issues2).toHaveLength(1); expect(issues2[0].title).toBe('Issue Integration test1'); }); }); diff --git a/test/integration/services/Labels.ts b/test/integration/services/Labels.ts new file mode 100644 index 00000000..0463b415 --- /dev/null +++ b/test/integration/services/Labels.ts @@ -0,0 +1,78 @@ +import { Labels, Projects } from '../../../dist'; + +const config = { + host: process.env.GITLAB_URL, + token: process.env.PERSONAL_ACCESS_TOKEN, +}; +let project; +let service: Labels; + +beforeAll(async () => { + // Crease project service + const projectService = new Projects(config); + + // Create issue service + service = new Labels(config); + + // Create a template project + project = await projectService.create({ name: 'Labels Integration test' }); +}); + +describe('Labels.create', () => { + it('should create a valid label on a project', async () => { + const label = await service.create(project.id, 'Test Label1', '#FFAABB'); + + expect(label).toBeInstanceOf(Object); + expect(label.name).toBe('Test Label1'); + }); +}); + +describe('Labels.remove', () => { + it('should remove/delete a valid label on a project', async () => { + const label = await service.create(project.id, 'Test Label3', '#FFAABB'); + + expect(service.remove(project.id, label.name)).resolves.toEqual(""); + }); +}); + +describe('Labels.all', () => { + beforeAll(async () => { + const labels: object[] = []; + + for (let i = 0; i < 50; i++) { + labels.push(service.create(project.id, `All Labels ${i}`, '#FFAABB')); + } + + return Promise.all(labels); + }); + + it('should return a list of labels on a project', async () => { + const labels = await service.all(project.id, { perPage: 3 }); + const filtered = labels.filter(l => l.name.includes('All Labels')); + + expect(labels).toBeInstanceOf(Array); + expect(filtered).toHaveLength(50); + }); + + it('should return a list of labels on a project restricted to page 5', async () => { + const labels = await service.all(project.id, { perPage: 5, page: 5}); + + expect(labels).toBeInstanceOf(Array); + expect(labels).toHaveLength(5); + }); + + it('should return a list of labels on a project restricted to page 5 and show the pagination object', async () => { + const { data, pagination } = await service.all(project.id, { perPage: 5, page: 5, showPagination: true}); + + expect(data).toBeInstanceOf(Array); + expect(data).toHaveLength(5); + expect(pagination).toMatchObject({ + total: 51, // TODO: change this to not depend on previous data + previous: 4, + current: 5, + next: 6, + perPage: 5, + totalPages: 11, + }); + }); +}); \ No newline at end of file diff --git a/test/unit/infrastructure/RequestHelper.ts b/test/unit/infrastructure/RequestHelper.ts index 50522c0c..a3da61fe 100644 --- a/test/unit/infrastructure/RequestHelper.ts +++ b/test/unit/infrastructure/RequestHelper.ts @@ -11,112 +11,59 @@ const mockedGetBasic = () => ({ }, }); -const mockedGetExtended = (url, { query }) => { - const pages = [ - { - body: [ - { - prop1: 1, - prop2: 'test property1', - }, - { - prop1: 2, - prop2: 'test property2', - }, - ], - headers: { - link: `; rel="next", - ; rel="first", - ; rel="last"`, - 'x-next-page': 2, - 'x-page': 1, - 'x-per-page': 2, - 'x-prev-page': '', - 'x-total': 8, - 'x-total-pages': 4, - }, - }, - { - body: [ - { - prop1: 3, - prop2: 'test property3', - }, - { - prop1: 4, - prop2: 'test property4', - }, - ], - headers: { - link: `; rel="prev", - ; rel="next", - ; rel="first", - ; rel="last"`, - 'x-next-page': 3, - 'x-page': 2, - 'x-per-page': 2, - 'x-prev-page': 1, - 'x-total': 8, - 'x-total-pages': 4, - }, - }, - { - body: [ - { - prop1: 5, - prop2: 'test property5', - }, - { - prop1: 6, - prop2: 'test property6', - }, - ], - headers: { - link: `; rel="prev", - ; rel="next", - ; rel="first", - ; rel="last"`, - 'x-next-page': 4, - 'x-page': 3, - 'x-per-page': 2, - 'x-prev-page': 2, - 'x-total': 8, - 'x-total-pages': 4, - }, - }, - { - body: [ - { - prop1: 7, - prop2: 'test property7', - }, - { - prop1: 8, - prop2: 'test property8', - }, - ], - headers: { - link: `; rel="prev", - ; rel="first", - ; rel="last"`, - 'x-next-page': '', - 'x-page': 4, - 'x-per-page': 2, - 'x-prev-page': 3, - 'x-total': 8, - 'x-total-pages': 4, - }, - }, - ]; - +const mockedGetExtended = (url, { query }, limit=30) => { + const pages: object[] = []; const q = url.match(/page=([0-9]+)/); let page = 1; if (q != null) page = q[1]; else if (query.page) page = query.page; + // Only load pages needed for the test + // TODO: modify this to only generate the required response, without the loop + for (let i = 1, a = 1, b = 2; i <= limit && i <= page; i++, a+=2, b+=2) { + let next = ''; + let prev = ''; + let nextPage; + let prevPage; + + if ((i+1) <= limit ) { + next = `; rel="next",`; + nextPage = i+1 + } + + if ((i-1) >= 1) { + prev = `; rel="prev",`; + prevPage = i-1 + } + + pages.push({ + body: [ + { + prop1: a, + prop2: `test property ${a}`, + }, + { + prop1: b, + prop2: `test property ${b}`, + }, + ], + headers: { + link: next + prev + ` + ; rel="first", + ; rel="last"`, + 'x-next-page': nextPage, + 'x-page': i, + 'x-per-page': 2, + 'x-prev-page': prevPage, + 'x-total': limit * 2, + 'x-total-pages': limit, + }, + }); + } + return pages[page - 1]; -}; +} const service = new BaseService({ host: 'https://testing.com', @@ -151,10 +98,23 @@ describe('RequestHelper.get()', () => { response.forEach((l, index) => { expect(l.prop1).toBe(1 + index); - expect(l.prop2).toBe(`test property${1 + index}`); + expect(l.prop2).toBe(`test property ${1 + index}`); }); - expect(response).toHaveLength(8); + expect(response).toHaveLength(60); + }); + + it('Should handle large paginated (50 pages) results when links are present', async () => { + KyRequester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options, 70)); + + const response = await RequestHelper.get(service, 'test'); + + response.forEach((l, index) => { + expect(l.prop1).toBe(1 + index); + expect(l.prop2).toBe(`test property ${1 + index}`); + }); + + expect(response).toHaveLength(140); }); it('Should be paginated but limited by the maxPages option', async () => { @@ -166,7 +126,7 @@ describe('RequestHelper.get()', () => { response.forEach((l, index) => { expect(l.prop1).toBe(1 + index); - expect(l.prop2).toBe(`test property${1 + index}`); + expect(l.prop2).toBe(`test property ${1 + index}`); }); }); @@ -179,7 +139,7 @@ describe('RequestHelper.get()', () => { response.forEach((l, index) => { expect(l.prop1).toBe(3 + index); - expect(l.prop2).toBe(`test property${3 + index}`); + expect(l.prop2).toBe(`test property ${3 + index}`); }); }); @@ -192,16 +152,16 @@ describe('RequestHelper.get()', () => { response.data.forEach((l, index) => { expect(l.prop1).toBe(3 + index); - expect(l.prop2).toBe(`test property${3 + index}`); + expect(l.prop2).toBe(`test property ${3 + index}`); }); expect(response.pagination).toMatchObject({ - total: 8, + total: 60, previous: 1, current: 2, next: 3, perPage: 2, - totalPages: 4, + totalPages: 30, }); }); @@ -217,16 +177,16 @@ describe('RequestHelper.get()', () => { response.data.forEach((l, index) => { expect(l.prop1).toBe(1 + index); - expect(l.prop2).toBe(`test property${1 + index}`); + expect(l.prop2).toBe(`test property ${1 + index}`); }); expect(response.pagination).toMatchObject({ - total: 8, + total: 60, previous: 2, current: 3, next: 4, perPage: 2, - totalPages: 4, + totalPages: 30, }); }); });