diff --git a/clients/typescript/__tests__/BasicApiTest.ts b/clients/typescript/__tests__/BasicApiTest.ts index 2d7ef1625..95b2ba4e9 100644 --- a/clients/typescript/__tests__/BasicApiTest.ts +++ b/clients/typescript/__tests__/BasicApiTest.ts @@ -6,12 +6,15 @@ const fs = require("fs"); global.FormData = FormData; -const getMockFetch = (jsonResponse, textResponse) => jest.fn(() => Promise.resolve({ +const getMockFetch = (jsonResponse, textResponse, headers) => jest.fn(() => Promise.resolve({ json: () => Promise.resolve(jsonResponse), text: () => Promise.resolve(textResponse), blob: () => Promise.resolve(textResponse), status: 200, - ok: true + ok: true, + headers: { + get: (name: string) => headers[name.toLowerCase()] + } })) as jest.Mock; describe('LocalesApi', () => { @@ -21,7 +24,7 @@ describe('LocalesApi', () => { describe('localesCreate', () => { beforeEach(() => { - mockFetch = getMockFetch({}, 'foo'); + mockFetch = getMockFetch({}, 'foo', {}); configuration = new Configuration( { apiKey: `token PHRASE_TOKEN`, @@ -46,7 +49,7 @@ describe('LocalesApi', () => { describe('localesList', () => { beforeEach(() => { - mockFetch = getMockFetch([{id: 'locale_id_1'}], 'foo'); + mockFetch = getMockFetch([{id: 'locale_id_1'}], 'foo', {}); configuration = new Configuration( { apiKey: `token PHRASE_TOKEN`, @@ -59,8 +62,43 @@ describe('LocalesApi', () => { test('lists locales', async () => { const projectId = 'my-project-id'; - await api.localesList({projectId}).then((response) => { - expect(response[0].id).toBe('locale_id_1'); + await api.localesList({projectId}).then((data) => { + expect(data[0].id).toBe('locale_id_1'); + }); + + expect(mockFetch.mock.calls.length).toBe(1); + expect(mockFetch.mock.calls[0][0]).toBe(`https://api.phrase.com/v2/projects/${projectId}/locales`); + }); + }); + + describe('localesListRaw', () => { + beforeEach(() => { + mockFetch = getMockFetch([{id: 'locale_id_1'}], 'foo', { + pagination: '{"total_count":59,"total_pages_count":3,"current_page":1,"current_per_page":25,"previous_page":null,"next_page":2}', + link: '; rel=first, ; rel=last, ; rel=next' + }); + configuration = new Configuration( + { + apiKey: `token PHRASE_TOKEN`, + fetchApi: mockFetch + } + ); + api = new LocalesApi(configuration); + }); + + test('lists locales and checks pagination', async () => { + const projectId = 'my-project-id'; + + await api.localesListRaw({projectId}).then((response) => { + expect(response.isPaginated).toBe(true); + expect(response.hasNextPage).toBe(true); + expect(response.nextPageUrl).toBe('https://api.phrase.com/v2/projects/my-project-id/locales?page=2'); + expect(response.totalCount).toBe(59); + expect(response.totalPages).toBe(3); + expect(response.nextPage).toBe(2); + response.value().then((data) => { + expect(data[0].id).toBe('locale_id_1'); + }); }); expect(mockFetch.mock.calls.length).toBe(1); @@ -76,7 +114,7 @@ describe('UploadsApi', () => { describe('uploadCreate', () => { beforeEach(() => { - mockFetch = getMockFetch({id: "upload_id"}, 'foo'); + mockFetch = getMockFetch({id: "upload_id"}, 'foo', {}); configuration = new Configuration( { apiKey: `token PHRASE_TOKEN`, diff --git a/openapi-generator/templates/typescript-fetch/runtime.mustache b/openapi-generator/templates/typescript-fetch/runtime.mustache index f1ac4a9bc..6a0b799b7 100644 --- a/openapi-generator/templates/typescript-fetch/runtime.mustache +++ b/openapi-generator/templates/typescript-fetch/runtime.mustache @@ -288,14 +288,57 @@ export interface Middleware { export interface ApiResponse { raw: Response; value(): Promise; + isPaginated?: boolean; + hasNextPage?: boolean; + nextPage?: number; + nextPageUrl?: string; + totalCount?: number; + totalPages?: number; } export interface ResponseTransformer { (json: any): T; } -export class JSONApiResponse { - constructor(public raw: Response, private transformer: ResponseTransformer = (jsonValue: any) => jsonValue) {} +export class JSONApiResponse implements ApiResponse { + public isPaginated: boolean; + public hasNextPage: boolean; + public nextPage?: number; + public nextPageUrl?: string; + public totalCount?: number; + public totalPages?: number; + + constructor(public raw: Response, private transformer: ResponseTransformer = (jsonValue: any) => jsonValue) { + const link = raw.headers.get('Link') || raw.headers.get('link'); + const pagination = raw.headers.get('Pagination') || raw.headers.get('pagination'); + this.isPaginated = !!link; + this.hasNextPage = false; + if (link) { + const match = link.match(/<([^>]+)>;\s*rel=next/); + if (match) { + this.hasNextPage = true; + this.nextPageUrl = match[1]; + } + } + if (pagination) { + try { + const paginationObj = JSON.parse(pagination); + if (paginationObj) { + if (typeof paginationObj['total_count'] === 'number') { + this.totalCount = paginationObj['total_count']; + } + if (typeof paginationObj['total_pages_count'] === 'number') { + this.totalPages = paginationObj['total_pages_count']; + } + if (typeof paginationObj['next_page'] === 'number') { + this.nextPage = paginationObj['next_page']; + } + } + } catch (e) { + // ignore invalid pagination header + } + } + } async value(): Promise { return this.transformer(await this.raw.json());