Skip to content

Commit beaa0c9

Browse files
Merge pull request #94 from FusionAuth/lyle/2513/fix-get-with-multilple-param
fix the typescript client not properly handling get query params with an array value
2 parents 0ac2d0e + e7239f0 commit beaa0c9

File tree

2 files changed

+96
-60
lines changed

2 files changed

+96
-60
lines changed

src/DefaultRESTClient.ts

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019-2020, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2019-2024, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
1414
* language governing permissions and limitations under the License.
1515
*/
1616

17-
import IRESTClient, {ErrorResponseHandler, ResponseHandler} from "./IRESTClient";
17+
import IRESTClient, { ErrorResponseHandler, ResponseHandler } from "./IRESTClient";
1818
import ClientResponse from "./ClientResponse";
19-
import fetch, {BodyInit, RequestCredentials, Response} from 'node-fetch';
20-
import {URLSearchParams} from "url";
19+
import fetch, { BodyInit, RequestCredentials, Response } from 'node-fetch';
20+
import { URLSearchParams } from "url";
2121

2222
/**
2323
* @author Brett P
@@ -37,6 +37,42 @@ export default class DefaultRESTClient<RT, ERT> implements IRESTClient<RT, ERT>
3737
constructor(public host: string) {
3838
}
3939

40+
/**
41+
* A function that returns the JSON form of the response text.
42+
*
43+
* @param response
44+
* @constructor
45+
*/
46+
static async JSONResponseHandler<RT>(response: Response): Promise<ClientResponse<RT>> {
47+
let clientResponse = new ClientResponse<RT>();
48+
49+
clientResponse.statusCode = response.status;
50+
let type = response.headers.get("content-type");
51+
if (type && type.startsWith("application/json")) {
52+
clientResponse.response = await response.json();
53+
}
54+
55+
return clientResponse;
56+
}
57+
58+
/**
59+
* A function that returns the JSON form of the response text.
60+
*
61+
* @param response
62+
* @constructor
63+
*/
64+
static async ErrorJSONResponseHandler<ERT>(response: Response): Promise<ClientResponse<ERT>> {
65+
let clientResponse = new ClientResponse<ERT>();
66+
67+
clientResponse.statusCode = response.status;
68+
let type = response.headers.get("content-type");
69+
if (type && type.startsWith("application/json")) {
70+
clientResponse.exception = await response.json();
71+
}
72+
73+
return clientResponse;
74+
}
75+
4076
/**
4177
* Sets the authorization header using a key
4278
*
@@ -86,7 +122,7 @@ export default class DefaultRESTClient<RT, ERT> implements IRESTClient<RT, ERT>
86122
if (body) {
87123
body.forEach((value, name, searchParams) => {
88124
if (value && value.length > 0 && value != "null" && value != "undefined") {
89-
body2.set(name,value);
125+
body2.set(name, value);
90126
}
91127
});
92128
body = body2;
@@ -206,47 +242,19 @@ export default class DefaultRESTClient<RT, ERT> implements IRESTClient<RT, ERT>
206242
}
207243

208244
private getQueryString() {
209-
var queryString = '';
210-
for (let key in this.parameters) {
245+
let queryString = '';
246+
const appendParam = (key: string, param: string) => {
211247
queryString += (queryString.length === 0) ? '?' : '&';
212-
queryString += key + '=' + encodeURIComponent(this.parameters[key]);
248+
queryString += encodeURIComponent(key) + '=' + encodeURIComponent(param);
213249
}
214-
return queryString;
215-
}
216-
217-
/**
218-
* A function that returns the JSON form of the response text.
219-
*
220-
* @param response
221-
* @constructor
222-
*/
223-
static async JSONResponseHandler<RT>(response: Response): Promise<ClientResponse<RT>> {
224-
let clientResponse = new ClientResponse<RT>();
225-
226-
clientResponse.statusCode = response.status;
227-
let type = response.headers.get("content-type");
228-
if (type && type.startsWith("application/json")) {
229-
clientResponse.response = await response.json();
230-
}
231-
232-
return clientResponse;
233-
}
234-
235-
/**
236-
* A function that returns the JSON form of the response text.
237-
*
238-
* @param response
239-
* @constructor
240-
*/
241-
static async ErrorJSONResponseHandler<ERT>(response: Response): Promise<ClientResponse<ERT>> {
242-
let clientResponse = new ClientResponse<ERT>();
243-
244-
clientResponse.statusCode = response.status;
245-
let type = response.headers.get("content-type");
246-
if (type && type.startsWith("application/json")) {
247-
clientResponse.exception = await response.json();
250+
for (let key in this.parameters) {
251+
const value = this.parameters[key];
252+
if (Array.isArray(value)) {
253+
value.forEach(val => appendParam(key, val))
254+
} else {
255+
appendParam(key, value);
256+
}
248257
}
249-
250-
return clientResponse;
258+
return queryString;
251259
}
252260
}

test/FusionAuthClientTest.ts

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2019-2024, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
1616

1717
'use strict';
1818

19-
import {ApplicationRequest, FusionAuthClient, GrantType} from '../index';
19+
import { ApplicationRequest, FusionAuthClient, GrantType, SearchResponse } from '../index';
2020
import * as chai from 'chai';
2121
import ClientResponse from "../src/ClientResponse";
2222

@@ -114,7 +114,7 @@ describe('#FusionAuthClient()', function () {
114114
}
115115
});
116116

117-
it('Create, Patch and Delete a User', async () => {
117+
it('Create, Patch, Search, and Delete a User', async () => {
118118
let clientResponse = await client.createUser(null, {
119119
user: {
120120
email: 'nodejs@fusionauth.io',
@@ -130,8 +130,10 @@ describe('#FusionAuthClient()', function () {
130130
chai.expect(clientResponse.response).to.have.property('user');
131131
chai.expect(clientResponse.response.user).to.have.property('id');
132132

133+
const userId = clientResponse.response.user.id;
134+
133135
// Patch the user
134-
clientResponse = await client.patchUser(clientResponse.response.user.id, {
136+
clientResponse = await client.patchUser(userId, {
135137
user: {
136138
firstName: "Jan"
137139
}
@@ -142,20 +144,46 @@ describe('#FusionAuthClient()', function () {
142144
chai.expect(clientResponse.response).to.have.property('user');
143145
chai.expect(clientResponse.response.user.firstName).to.equal("Jan");
144146

145-
clientResponse = await client.deleteUser(clientResponse.response.user.id);
146-
chai.assert.strictEqual(clientResponse.statusCode, 200);
147-
// Browser will return empty, node will return null, account for both scenarios
148-
if (clientResponse.response === null) {
149-
chai.assert.isNull(clientResponse.response);
150-
} else {
151-
chai.assert.isUndefined(clientResponse.response);
147+
// create a second user and search them both
148+
clientResponse = await client.createUser(null, {
149+
user: {
150+
email: 'node2@fusionauth.io',
151+
firstName: 'Joan',
152+
password: 'password'
153+
},
154+
skipVerification: true,
155+
sendSetPasswordEmail: false
156+
});
157+
158+
const secondUserId = clientResponse.response.user.id;
159+
const bothUsers = [userId, secondUserId];
160+
161+
const searchResp: ClientResponse<SearchResponse> = await client.searchUsersByIds(bothUsers);
162+
chai.assert.strictEqual(searchResp.statusCode, 200);
163+
chai.assert.strictEqual(searchResp.response.total, 2);
164+
// make sure each user was returned
165+
bothUsers.forEach(id => chai.assert.isNotNull(searchResp.response.users.find(user => user.id = id)));
166+
167+
// delete both users
168+
for (const id of bothUsers) {
169+
clientResponse = await client.deleteUser(id);
170+
chai.assert.strictEqual(clientResponse.statusCode, 200);
171+
// Browser will return empty, node will return null, account for both scenarios
172+
if (clientResponse.response === null) {
173+
chai.assert.isNull(clientResponse.response);
174+
} else {
175+
chai.assert.isUndefined(clientResponse.response);
176+
}
152177
}
153178

154-
try {
155-
await client.retrieveUserByEmail('nodejs@fusionauth.io');
156-
chai.expect.fail("The user should have been deleted!");
157-
} catch (clientResponse) {
158-
chai.assert.strictEqual(clientResponse.statusCode, 404);
179+
// check that they are gone
180+
for (const email of ['nodejs@fusionauth.io', 'node2@fusionauth.io']) {
181+
try {
182+
await client.retrieveUserByEmail(email);
183+
chai.expect.fail(`The user with ${email} should have been deleted!`);
184+
} catch (clientResponse) {
185+
chai.assert.strictEqual(clientResponse.statusCode, 404);
186+
}
159187
}
160188
});
161189

0 commit comments

Comments
 (0)