From c10c76941af0866578843ea755435ad83db95667 Mon Sep 17 00:00:00 2001 From: Scott Lovegrove Date: Tue, 13 Sep 2022 17:59:29 +0100 Subject: [PATCH 1/4] fix: Corrects the call when an array of ids is used --- src/TodoistApi.ts | 3 ++- src/restClient.axios.test.ts | 18 ++++++++++++++++++ src/restClient.test.ts | 18 ++++++++++++------ src/restClient.ts | 22 ++++++++++++++++++++-- 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 src/restClient.axios.test.ts diff --git a/src/TodoistApi.ts b/src/TodoistApi.ts index ae816d1..7f232f7 100644 --- a/src/TodoistApi.ts +++ b/src/TodoistApi.ts @@ -240,6 +240,7 @@ export class TodoistApi { this.restApiBase, generatePath(ENDPOINT_REST_PROJECTS, id), this.authToken, + undefined, requestId, ) return isSuccess(response) @@ -263,7 +264,7 @@ export class TodoistApi { this.restApiBase, ENDPOINT_REST_SECTIONS, this.authToken, - projectId && { projectId }, + projectId ? { projectId } : undefined, ) return validateSectionArray(response.data) diff --git a/src/restClient.axios.test.ts b/src/restClient.axios.test.ts new file mode 100644 index 0000000..5cb1a2a --- /dev/null +++ b/src/restClient.axios.test.ts @@ -0,0 +1,18 @@ +import axios from 'axios' +import { paramsSerializer } from './restClient' + +const DEFAULT_BASE_URI = 'https://api.todoist.com/rest/v2/tasks' + +describe('axios tests without mocking', () => { + test('GET calls serialise arrays correctly', () => { + const requestUri = axios.create().getUri({ + method: 'GET', + baseURL: DEFAULT_BASE_URI, + params: { + ids: ['12345', '56789'], + }, + paramsSerializer, + }) + expect(requestUri).toEqual('https://api.todoist.com/rest/v2/tasks?ids=12345%2C56789') + }) +}) diff --git a/src/restClient.test.ts b/src/restClient.test.ts index b8f25b2..3c8e6d0 100644 --- a/src/restClient.test.ts +++ b/src/restClient.test.ts @@ -118,9 +118,12 @@ describe('restClient', () => { await request('GET', DEFAULT_BASE_URI, DEFAULT_ENDPOINT, DEFAULT_AUTH_TOKEN) expect(axiosMock.get).toBeCalledTimes(1) - expect(axiosMock.get).toBeCalledWith(DEFAULT_ENDPOINT, { - params: undefined, - }) + expect(axiosMock.get).toBeCalledWith( + DEFAULT_ENDPOINT, + expect.objectContaining({ + params: undefined, + }), + ) }) test('get passes params to axios', async () => { @@ -133,9 +136,12 @@ describe('restClient', () => { ) expect(axiosMock.get).toBeCalledTimes(1) - expect(axiosMock.get).toBeCalledWith(DEFAULT_ENDPOINT, { - params: DEFAULT_PAYLOAD, - }) + expect(axiosMock.get).toBeCalledWith( + DEFAULT_ENDPOINT, + expect.objectContaining({ + params: DEFAULT_PAYLOAD, + }), + ) }) test('get returns response from axios', async () => { diff --git a/src/restClient.ts b/src/restClient.ts index 96a3b3a..ac836eb 100644 --- a/src/restClient.ts +++ b/src/restClient.ts @@ -5,6 +5,21 @@ import { HttpMethod } from './types/http' import { v4 as uuidv4 } from 'uuid' import axiosRetry from 'axios-retry' +export function paramsSerializer(params: Record) { + const qs = new URLSearchParams() + + Object.keys(params).forEach((key) => { + const value = params[key] + if (Array.isArray(value)) { + qs.append(key, value.join(',')) + } else { + qs.append(key, String(value)) + } + }) + + return qs.toString() +} + const defaultHeaders = { 'Content-Type': 'application/json', } @@ -71,7 +86,7 @@ export async function request( baseUri: string, relativePath: string, apiToken?: string, - payload?: unknown, + payload?: Record, requestId?: string, ): Promise> { // axios loses the original stack when returning errors, for the sake of better reporting @@ -88,7 +103,10 @@ export async function request( switch (httpMethod) { case 'GET': - return await axiosClient.get(relativePath, { params: payload }) + return await axiosClient.get(relativePath, { + params: payload, + paramsSerializer, + }) case 'POST': return await axiosClient.post(relativePath, payload) case 'DELETE': From 42bfe2042439901b8f62d04491bd1409539347be Mon Sep 17 00:00:00 2001 From: Scott Lovegrove Date: Tue, 13 Sep 2022 18:03:29 +0100 Subject: [PATCH 2/4] chore: Updates package version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfaf5bb..8b5aaea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@doist/todoist-api-typescript", - "version": "2.0.2", + "version": "2.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@doist/todoist-api-typescript", - "version": "2.0.2", + "version": "2.0.3", "license": "MIT", "dependencies": { "axios": "^0.27.0", diff --git a/package.json b/package.json index 66b87a3..b8f3b71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@doist/todoist-api-typescript", - "version": "2.0.2", + "version": "2.0.3", "description": "A typescript wrapper for the Todoist REST API.", "author": "Doist developers", "repository": "git@github.com:doist/todoist-api-typescript.git", From 696b7bcf120381a7afa9a220d55dbfc046cfcd7a Mon Sep 17 00:00:00 2001 From: Scott Lovegrove Date: Tue, 13 Sep 2022 18:07:26 +0100 Subject: [PATCH 3/4] tests: Fixes test --- src/TodoistApi.projects.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TodoistApi.projects.test.ts b/src/TodoistApi.projects.test.ts index 5fcd24e..cc07b69 100644 --- a/src/TodoistApi.projects.test.ts +++ b/src/TodoistApi.projects.test.ts @@ -150,6 +150,7 @@ describe('TodoistApi project endpoints', () => { getRestBaseUri(), `${ENDPOINT_REST_PROJECTS}/${projectId}`, DEFAULT_AUTH_TOKEN, + undefined, DEFAULT_REQUEST_ID, ) }) From 951bab5d1f87cf93c1032ef0c32e56591fbe56cd Mon Sep 17 00:00:00 2001 From: Scott Lovegrove Date: Wed, 14 Sep 2022 09:35:36 +0100 Subject: [PATCH 4/4] tests: Updates tests to ensure that paramsSerializer is included --- src/restClient.test.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/restClient.test.ts b/src/restClient.test.ts index 3c8e6d0..fcc87a3 100644 --- a/src/restClient.test.ts +++ b/src/restClient.test.ts @@ -1,5 +1,5 @@ import Axios, { AxiosStatic, AxiosResponse, AxiosError } from 'axios' -import { request, isSuccess } from './restClient' +import { request, isSuccess, paramsSerializer } from './restClient' import { TodoistRequestError } from './types/errors' import * as caseConverter from 'axios-case-converter' import { assertInstance } from './testUtils/asserts' @@ -118,12 +118,10 @@ describe('restClient', () => { await request('GET', DEFAULT_BASE_URI, DEFAULT_ENDPOINT, DEFAULT_AUTH_TOKEN) expect(axiosMock.get).toBeCalledTimes(1) - expect(axiosMock.get).toBeCalledWith( - DEFAULT_ENDPOINT, - expect.objectContaining({ - params: undefined, - }), - ) + expect(axiosMock.get).toBeCalledWith(DEFAULT_ENDPOINT, { + params: undefined, + paramsSerializer, + }) }) test('get passes params to axios', async () => { @@ -136,12 +134,10 @@ describe('restClient', () => { ) expect(axiosMock.get).toBeCalledTimes(1) - expect(axiosMock.get).toBeCalledWith( - DEFAULT_ENDPOINT, - expect.objectContaining({ - params: DEFAULT_PAYLOAD, - }), - ) + expect(axiosMock.get).toBeCalledWith(DEFAULT_ENDPOINT, { + params: DEFAULT_PAYLOAD, + paramsSerializer, + }) }) test('get returns response from axios', async () => {