feat(aio): implement `GithubApi.getPaginated()`
This commit is contained in:
parent
37348989f0
commit
951e653b0c
|
@ -51,6 +51,23 @@ export class GithubApi {
|
||||||
return `${pathname}${joiner}${search}`;
|
return `${pathname}${joiner}${search}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getPaginated<T>(pathname: string, baseParams: RequestParams = {}, currentPage: number = 0): Promise<T[]> {
|
||||||
|
const perPage = 100;
|
||||||
|
const params = {
|
||||||
|
...baseParams,
|
||||||
|
page: currentPage,
|
||||||
|
per_page: perPage,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.get<T[]>(pathname, params).then(items => {
|
||||||
|
if (items.length < perPage) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getPaginated(pathname, baseParams, currentPage + 1).then(moreItems => [...items, ...moreItems]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected request<T>(method: string, path: string, data: any = null): Promise<T> {
|
protected request<T>(method: string, path: string, data: any = null): Promise<T> {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
const options = {
|
const options = {
|
||||||
|
|
|
@ -22,30 +22,11 @@ export class GithubPullRequests extends GithubApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchAll(state: PullRequestState = 'all'): Promise<PullRequest[]> {
|
public fetchAll(state: PullRequestState = 'all'): Promise<PullRequest[]> {
|
||||||
process.stdout.write(`Fetching ${state} pull requests...`);
|
console.log(`Fetching ${state} pull requests...`);
|
||||||
return this.fetchUntilDone(state, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods - Protected
|
|
||||||
protected fetchUntilDone(state: PullRequestState, currentPage: number): Promise<PullRequest[]> {
|
|
||||||
process.stdout.write('.');
|
|
||||||
|
|
||||||
const perPage = 100;
|
|
||||||
const pathname = `/repos/${this.repoSlug}/pulls`;
|
const pathname = `/repos/${this.repoSlug}/pulls`;
|
||||||
const params = {
|
const params = {state};
|
||||||
page: currentPage,
|
|
||||||
per_page: perPage,
|
|
||||||
state,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.get<PullRequest[]>(pathname, params).then(pullRequests => {
|
return this.getPaginated<PullRequest>(pathname, params);
|
||||||
if (pullRequests.length < perPage) {
|
|
||||||
console.log('done');
|
|
||||||
return pullRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.fetchUntilDone(state, currentPage + 1).
|
|
||||||
then(morePullRequests => [...pullRequests, ...morePullRequests]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,81 @@ describe('GithubApi', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('getPaginated()', () => {
|
||||||
|
let deferreds: {resolve: Function, reject: Function}[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
deferreds = [];
|
||||||
|
spyOn(api, 'get').and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject})));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should return a promise', () => {
|
||||||
|
expect((api as any).getPaginated()).toEqual(jasmine.any(Promise));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should call \'get()\' with the correct pathname and params', () => {
|
||||||
|
(api as any).getPaginated('/foo/bar');
|
||||||
|
(api as any).getPaginated('/foo/bar', {baz: 'qux'});
|
||||||
|
|
||||||
|
expect(api.get).toHaveBeenCalledWith('/foo/bar', {page: 0, per_page: 100});
|
||||||
|
expect(api.get).toHaveBeenCalledWith('/foo/bar', {baz: 'qux', page: 0, per_page: 100});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should reject if the request fails', done => {
|
||||||
|
(api as any).getPaginated('/foo/bar').catch(err => {
|
||||||
|
expect(err).toBe('Test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
deferreds[0].reject('Test');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should resolve with the returned items', done => {
|
||||||
|
const items = [{id: 1}, {id: 2}];
|
||||||
|
|
||||||
|
(api as any).getPaginated('/foo/bar').then((data: any) => {
|
||||||
|
expect(data).toEqual(items);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
deferreds[0].resolve(items);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should iteratively call \'get()\' to fetch all items', done => {
|
||||||
|
// Create an array or 250 objects.
|
||||||
|
const allItems = '.'.repeat(250).split('').map((_, i) => ({id: i}));
|
||||||
|
const apiGetSpy = api.get as jasmine.Spy;
|
||||||
|
|
||||||
|
(api as any).getPaginated('/foo/bar', {baz: 'qux'}).then((data: any) => {
|
||||||
|
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
|
||||||
|
|
||||||
|
expect(apiGetSpy).toHaveBeenCalledTimes(3);
|
||||||
|
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(0)]);
|
||||||
|
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(1)]);
|
||||||
|
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(2)]);
|
||||||
|
|
||||||
|
expect(data).toEqual(allItems);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
deferreds[0].resolve(allItems.slice(0, 100));
|
||||||
|
setTimeout(() => {
|
||||||
|
deferreds[1].resolve(allItems.slice(100, 200));
|
||||||
|
setTimeout(() => {
|
||||||
|
deferreds[2].resolve(allItems.slice(200));
|
||||||
|
}, 0);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('request()', () => {
|
describe('request()', () => {
|
||||||
let httpsRequestSpy: jasmine.Spy;
|
let httpsRequestSpy: jasmine.Spy;
|
||||||
let latestRequest: ClientRequest;
|
let latestRequest: ClientRequest;
|
||||||
|
|
|
@ -78,89 +78,38 @@ describe('GithubPullRequests', () => {
|
||||||
|
|
||||||
describe('fetchAll()', () => {
|
describe('fetchAll()', () => {
|
||||||
let prs: GithubPullRequests;
|
let prs: GithubPullRequests;
|
||||||
let deferreds: {resolve: Function, reject: Function}[];
|
let prsGetPaginatedSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
prs = new GithubPullRequests('foo/bar', '12345');
|
prs = new GithubPullRequests('foo/bar', '12345');
|
||||||
deferreds = [];
|
prsGetPaginatedSpy = spyOn(prs as any, 'getPaginated');
|
||||||
|
spyOn(console, 'log');
|
||||||
spyOn(process.stdout, 'write');
|
|
||||||
spyOn(prs, 'get').and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject})));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should return a promise', () => {
|
it('should call \'getPaginated()\' with the correct pathname and params', () => {
|
||||||
expect(prs.fetchAll()).toEqual(jasmine.any(Promise));
|
const expectedPathname = '/repos/foo/bar/pulls';
|
||||||
});
|
|
||||||
|
|
||||||
|
prs.fetchAll('all');
|
||||||
it('should call \'get()\' with the correct pathname and params', () => {
|
prs.fetchAll('closed');
|
||||||
prs.fetchAll('open');
|
prs.fetchAll('open');
|
||||||
|
|
||||||
expect(prs.get).toHaveBeenCalledWith('/repos/foo/bar/pulls', {
|
expect(prsGetPaginatedSpy).toHaveBeenCalledTimes(3);
|
||||||
page: 0,
|
expect(prsGetPaginatedSpy.calls.argsFor(0)).toEqual([expectedPathname, {state: 'all'}]);
|
||||||
per_page: 100,
|
expect(prsGetPaginatedSpy.calls.argsFor(1)).toEqual([expectedPathname, {state: 'closed'}]);
|
||||||
state: 'open',
|
expect(prsGetPaginatedSpy.calls.argsFor(2)).toEqual([expectedPathname, {state: 'open'}]);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should default to \'all\' if no state is specified', () => {
|
it('should default to \'all\' if no state is specified', () => {
|
||||||
prs.fetchAll();
|
prs.fetchAll();
|
||||||
|
expect(prsGetPaginatedSpy).toHaveBeenCalledWith('/repos/foo/bar/pulls', {state: 'all'});
|
||||||
expect(prs.get).toHaveBeenCalledWith('/repos/foo/bar/pulls', {
|
|
||||||
page: 0,
|
|
||||||
per_page: 100,
|
|
||||||
state: 'all',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should reject if the request fails', done => {
|
it('should forward the value returned by \'getPaginated()\'', () => {
|
||||||
prs.fetchAll().catch(err => {
|
prsGetPaginatedSpy.and.returnValue('Test');
|
||||||
expect(err).toBe('Test');
|
expect(prs.fetchAll()).toBe('Test');
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
deferreds[0].reject('Test');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should resolve with the returned pull requests', done => {
|
|
||||||
const pullRequests = [{id: 1}, {id: 2}];
|
|
||||||
|
|
||||||
prs.fetchAll().then(data => {
|
|
||||||
expect(data).toEqual(pullRequests);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
deferreds[0].resolve(pullRequests);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should iteratively call \'get()\' to fetch all pull requests', done => {
|
|
||||||
// Create an array or 250 objects.
|
|
||||||
const allPullRequests = '.'.repeat(250).split('').map((_, i) => ({id: i}));
|
|
||||||
const prsGetApy = prs.get as jasmine.Spy;
|
|
||||||
|
|
||||||
prs.fetchAll().then(data => {
|
|
||||||
const paramsForPage = (page: number) => ({page, per_page: 100, state: 'all'});
|
|
||||||
|
|
||||||
expect(prsGetApy).toHaveBeenCalledTimes(3);
|
|
||||||
expect(prsGetApy.calls.argsFor(0)).toEqual(['/repos/foo/bar/pulls', paramsForPage(0)]);
|
|
||||||
expect(prsGetApy.calls.argsFor(1)).toEqual(['/repos/foo/bar/pulls', paramsForPage(1)]);
|
|
||||||
expect(prsGetApy.calls.argsFor(2)).toEqual(['/repos/foo/bar/pulls', paramsForPage(2)]);
|
|
||||||
|
|
||||||
expect(data).toEqual(allPullRequests);
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
deferreds[0].resolve(allPullRequests.slice(0, 100));
|
|
||||||
setTimeout(() => {
|
|
||||||
deferreds[1].resolve(allPullRequests.slice(100, 200));
|
|
||||||
setTimeout(() => deferreds[2].resolve(allPullRequests.slice(200)), 0);
|
|
||||||
}, 0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue