Skip to content

Commit

Permalink
Merge pull request #94 from FusionAuth/lyle/2513/fix-get-with-multilp…
Browse files Browse the repository at this point in the history
…le-param

fix the typescript client not properly handling get query params with an array value
  • Loading branch information
lyleschemmerling authored Jan 9, 2024
2 parents 0ac2d0e + e7239f0 commit beaa0c9
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 60 deletions.
96 changes: 52 additions & 44 deletions src/DefaultRESTClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2020, FusionAuth, All Rights Reserved
* Copyright (c) 2019-2024, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,10 +14,10 @@
* language governing permissions and limitations under the License.
*/

import IRESTClient, {ErrorResponseHandler, ResponseHandler} from "./IRESTClient";
import IRESTClient, { ErrorResponseHandler, ResponseHandler } from "./IRESTClient";
import ClientResponse from "./ClientResponse";
import fetch, {BodyInit, RequestCredentials, Response} from 'node-fetch';
import {URLSearchParams} from "url";
import fetch, { BodyInit, RequestCredentials, Response } from 'node-fetch';
import { URLSearchParams } from "url";

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

/**
* A function that returns the JSON form of the response text.
*
* @param response
* @constructor
*/
static async JSONResponseHandler<RT>(response: Response): Promise<ClientResponse<RT>> {
let clientResponse = new ClientResponse<RT>();

clientResponse.statusCode = response.status;
let type = response.headers.get("content-type");
if (type && type.startsWith("application/json")) {
clientResponse.response = await response.json();
}

return clientResponse;
}

/**
* A function that returns the JSON form of the response text.
*
* @param response
* @constructor
*/
static async ErrorJSONResponseHandler<ERT>(response: Response): Promise<ClientResponse<ERT>> {
let clientResponse = new ClientResponse<ERT>();

clientResponse.statusCode = response.status;
let type = response.headers.get("content-type");
if (type && type.startsWith("application/json")) {
clientResponse.exception = await response.json();
}

return clientResponse;
}

/**
* Sets the authorization header using a key
*
Expand Down Expand Up @@ -86,7 +122,7 @@ export default class DefaultRESTClient<RT, ERT> implements IRESTClient<RT, ERT>
if (body) {
body.forEach((value, name, searchParams) => {
if (value && value.length > 0 && value != "null" && value != "undefined") {
body2.set(name,value);
body2.set(name, value);
}
});
body = body2;
Expand Down Expand Up @@ -206,47 +242,19 @@ export default class DefaultRESTClient<RT, ERT> implements IRESTClient<RT, ERT>
}

private getQueryString() {
var queryString = '';
for (let key in this.parameters) {
let queryString = '';
const appendParam = (key: string, param: string) => {
queryString += (queryString.length === 0) ? '?' : '&';
queryString += key + '=' + encodeURIComponent(this.parameters[key]);
queryString += encodeURIComponent(key) + '=' + encodeURIComponent(param);
}
return queryString;
}

/**
* A function that returns the JSON form of the response text.
*
* @param response
* @constructor
*/
static async JSONResponseHandler<RT>(response: Response): Promise<ClientResponse<RT>> {
let clientResponse = new ClientResponse<RT>();

clientResponse.statusCode = response.status;
let type = response.headers.get("content-type");
if (type && type.startsWith("application/json")) {
clientResponse.response = await response.json();
}

return clientResponse;
}

/**
* A function that returns the JSON form of the response text.
*
* @param response
* @constructor
*/
static async ErrorJSONResponseHandler<ERT>(response: Response): Promise<ClientResponse<ERT>> {
let clientResponse = new ClientResponse<ERT>();

clientResponse.statusCode = response.status;
let type = response.headers.get("content-type");
if (type && type.startsWith("application/json")) {
clientResponse.exception = await response.json();
for (let key in this.parameters) {
const value = this.parameters[key];
if (Array.isArray(value)) {
value.forEach(val => appendParam(key, val))
} else {
appendParam(key, value);
}
}

return clientResponse;
return queryString;
}
}
60 changes: 44 additions & 16 deletions test/FusionAuthClientTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, FusionAuth, All Rights Reserved
* Copyright (c) 2019-2024, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,7 +16,7 @@

'use strict';

import {ApplicationRequest, FusionAuthClient, GrantType} from '../index';
import { ApplicationRequest, FusionAuthClient, GrantType, SearchResponse } from '../index';
import * as chai from 'chai';
import ClientResponse from "../src/ClientResponse";

Expand Down Expand Up @@ -114,7 +114,7 @@ describe('#FusionAuthClient()', function () {
}
});

it('Create, Patch and Delete a User', async () => {
it('Create, Patch, Search, and Delete a User', async () => {
let clientResponse = await client.createUser(null, {
user: {
email: '[email protected]',
Expand All @@ -130,8 +130,10 @@ describe('#FusionAuthClient()', function () {
chai.expect(clientResponse.response).to.have.property('user');
chai.expect(clientResponse.response.user).to.have.property('id');

const userId = clientResponse.response.user.id;

// Patch the user
clientResponse = await client.patchUser(clientResponse.response.user.id, {
clientResponse = await client.patchUser(userId, {
user: {
firstName: "Jan"
}
Expand All @@ -142,20 +144,46 @@ describe('#FusionAuthClient()', function () {
chai.expect(clientResponse.response).to.have.property('user');
chai.expect(clientResponse.response.user.firstName).to.equal("Jan");

clientResponse = await client.deleteUser(clientResponse.response.user.id);
chai.assert.strictEqual(clientResponse.statusCode, 200);
// Browser will return empty, node will return null, account for both scenarios
if (clientResponse.response === null) {
chai.assert.isNull(clientResponse.response);
} else {
chai.assert.isUndefined(clientResponse.response);
// create a second user and search them both
clientResponse = await client.createUser(null, {
user: {
email: '[email protected]',
firstName: 'Joan',
password: 'password'
},
skipVerification: true,
sendSetPasswordEmail: false
});

const secondUserId = clientResponse.response.user.id;
const bothUsers = [userId, secondUserId];

const searchResp: ClientResponse<SearchResponse> = await client.searchUsersByIds(bothUsers);
chai.assert.strictEqual(searchResp.statusCode, 200);
chai.assert.strictEqual(searchResp.response.total, 2);
// make sure each user was returned
bothUsers.forEach(id => chai.assert.isNotNull(searchResp.response.users.find(user => user.id = id)));

// delete both users
for (const id of bothUsers) {
clientResponse = await client.deleteUser(id);
chai.assert.strictEqual(clientResponse.statusCode, 200);
// Browser will return empty, node will return null, account for both scenarios
if (clientResponse.response === null) {
chai.assert.isNull(clientResponse.response);
} else {
chai.assert.isUndefined(clientResponse.response);
}
}

try {
await client.retrieveUserByEmail('[email protected]');
chai.expect.fail("The user should have been deleted!");
} catch (clientResponse) {
chai.assert.strictEqual(clientResponse.statusCode, 404);
// check that they are gone
for (const email of ['[email protected]', '[email protected]']) {
try {
await client.retrieveUserByEmail(email);
chai.expect.fail(`The user with ${email} should have been deleted!`);
} catch (clientResponse) {
chai.assert.strictEqual(clientResponse.statusCode, 404);
}
}
});

Expand Down

0 comments on commit beaa0c9

Please sign in to comment.