From 998d3a705aac0ebdd59bacdbae2f48924cad86ff Mon Sep 17 00:00:00 2001 From: Haoliang Yu Date: Mon, 17 Jun 2019 14:33:49 -0400 Subject: [PATCH 1/3] feat(portal): a new function to add members to a given group AFFECTS PACKAGES: @esri/arcgis-rest-portal ISSUES CLOSED: #584 --- CHANGELOG.md | 3 + package-lock.json | 96 ++++---- .../src/groups/add-users.ts | 141 ++++++++++++ packages/arcgis-rest-portal/src/index.ts | 1 + packages/arcgis-rest-portal/src/util/array.ts | 16 ++ .../test/groups/add-users.test.ts | 210 ++++++++++++++++++ .../test/util/array.test.ts | 30 +++ 7 files changed, 449 insertions(+), 48 deletions(-) create mode 100644 packages/arcgis-rest-portal/src/groups/add-users.ts create mode 100644 packages/arcgis-rest-portal/src/util/array.ts create mode 100644 packages/arcgis-rest-portal/test/groups/add-users.test.ts create mode 100644 packages/arcgis-rest-portal/test/util/array.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 38153a26c6..56e0ba6c30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### [Unreleased][HEAD] +* New Features + * A new [`addGroupUsers`](https://esri.github.io/arcgis-rest-js/api/portal/addGroupUsers/) method has been added to add members to a given group. + ## [2.0.3] - May 23rd 2019 ### @esri/arcgis-rest-auth diff --git a/package-lock.json b/package-lock.json index ab4c1c5e80..ad7c350fe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5267,21 +5267,21 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true @@ -5299,14 +5299,14 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, @@ -5324,35 +5324,35 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "2.6.9", - "resolved": "", + "resolved": false, "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, @@ -5369,21 +5369,21 @@ }, "delegates": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": "", + "resolved": false, "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -5393,14 +5393,14 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -5432,7 +5432,7 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true @@ -5449,7 +5449,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -5459,7 +5459,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -5470,21 +5470,21 @@ }, "inherits": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true }, "ini": { "version": "1.3.5", - "resolved": "", + "resolved": false, "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, @@ -5494,14 +5494,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, @@ -5511,7 +5511,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "", + "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true @@ -5539,7 +5539,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -5549,7 +5549,7 @@ }, "ms": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true @@ -5587,7 +5587,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -5616,7 +5616,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -5629,21 +5629,21 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, @@ -5653,21 +5653,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -5678,14 +5678,14 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true @@ -5705,7 +5705,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -5714,7 +5714,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -5747,14 +5747,14 @@ }, "safer-buffer": { "version": "2.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true @@ -5768,21 +5768,21 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, @@ -5794,7 +5794,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -5804,7 +5804,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, @@ -5814,7 +5814,7 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true @@ -5837,7 +5837,7 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true @@ -5854,7 +5854,7 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true diff --git a/packages/arcgis-rest-portal/src/groups/add-users.ts b/packages/arcgis-rest-portal/src/groups/add-users.ts new file mode 100644 index 0000000000..9c84ff6865 --- /dev/null +++ b/packages/arcgis-rest-portal/src/groups/add-users.ts @@ -0,0 +1,141 @@ +/* Copyright (c) 2017-2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { + request, + IRequestOptions, + ArcGISRequestError +} from "@esri/arcgis-rest-request"; + +import { getPortalUrl } from "../util/get-portal-url"; +import { chunk } from "../util/array"; + +export interface IAddGroupUsersOptions extends IRequestOptions { + /** + * Group ID + */ + id: string; + /** + * An array of usernames to be added to the group as group members + */ + users?: string[]; + /** + * An array of usernames to be added to the group as group admins + */ + admins?: string[]; +} + +export interface IAddGroupUsersResult { + /** + * An array of usernames that were not added + */ + notAdded?: string[]; + /** + * An array of request errors + */ + errors?: ArcGISRequestError[]; +} + +/** + * ```js + * import { addGroupUsers } from "@esri/arcgis-rest-portal"; + * // + * addGroupUsers({ + * id: groupId, + * users: ["username1", "username2"], + * admins: ["username3"], + * authentication + * }) + * .then(response); + * ``` + * Add users to a group. See the [REST Documentation](https://developers.arcgis.com/rest/users-groups-and-items/add-users-to-group.htm) for more information. + * + * @param requestOptions - Options for the request + * @returns A Promise + */ +export function addGroupUsers( + requestOptions: IAddGroupUsersOptions +): Promise { + const id = requestOptions.id; + const url = `${getPortalUrl(requestOptions)}/community/groups/${id}/addUsers`; + const baseOptions = Object.assign({}, requestOptions, { + admins: undefined, + users: undefined + }); + + const batchRequestOptions = [ + ..._prepareRequests("users", requestOptions.users, baseOptions), + ..._prepareRequests("admins", requestOptions.admins, baseOptions) + ]; + + const promises = batchRequestOptions.map(options => + _sendSafeRequest(url, options) + ); + + return Promise.all(promises).then(_consolidateRequestResults); +} + +function _prepareRequests( + type: "admins" | "users", + usernames: string[], + baseOptions: IAddGroupUsersOptions +): IAddGroupUsersOptions[] { + if (!usernames || usernames.length < 1) { + return []; + } + + // the ArcGIS REST API only allows to add no more than 25 users per request, + // see https://developers.arcgis.com/rest/users-groups-and-items/add-users-to-group.htm + const userChunks: string[][] = chunk(usernames, 25); + + return userChunks.map(users => + _generateRequestOptions(type, users, baseOptions) + ); +} + +function _generateRequestOptions( + type: "admins" | "users", + usernames: string[], + baseOptions: IAddGroupUsersOptions +) { + return Object.assign({}, baseOptions, { + [type]: usernames, + params: { + ...baseOptions.params, + [type]: usernames + } + }); +} + +function _sendSafeRequest( + url: string, + requestOptions: IAddGroupUsersOptions +): Promise { + return request(url, requestOptions).catch(error => { + return { + // the request should either add users or admins, not both + notAdded: requestOptions.users || requestOptions.admins, + errors: [error] + }; + }); +} + +function _consolidateRequestResults( + results: IAddGroupUsersResult[] +): IAddGroupUsersResult { + const notAdded = results + .filter(result => result.notAdded) + .reduce((collection, result) => collection.concat(result.notAdded), []); + + const errors = results + .filter(result => result.errors) + .reduce((collection, result) => collection.concat(result.errors), []); + + const consolidated: IAddGroupUsersResult = { notAdded }; + + if (errors.length > 0) { + consolidated.errors = errors; + } + + return consolidated; +} diff --git a/packages/arcgis-rest-portal/src/index.ts b/packages/arcgis-rest-portal/src/index.ts index 885321d735..9e446df927 100644 --- a/packages/arcgis-rest-portal/src/index.ts +++ b/packages/arcgis-rest-portal/src/index.ts @@ -10,6 +10,7 @@ export * from "./items/search"; export * from "./items/update"; export * from "./items/helpers"; +export * from "./groups/add-users"; export * from "./groups/create"; export * from "./groups/get"; export * from "./groups/helpers"; diff --git a/packages/arcgis-rest-portal/src/util/array.ts b/packages/arcgis-rest-portal/src/util/array.ts new file mode 100644 index 0000000000..0e670164e7 --- /dev/null +++ b/packages/arcgis-rest-portal/src/util/array.ts @@ -0,0 +1,16 @@ +/* Copyright (c) 2019 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +export function chunk(array: T[], size: number) { + if (array.length === 0) { + return []; + } + + const chunks = []; + + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)); + } + + return chunks; +} diff --git a/packages/arcgis-rest-portal/test/groups/add-users.test.ts b/packages/arcgis-rest-portal/test/groups/add-users.test.ts new file mode 100644 index 0000000000..67648ebb01 --- /dev/null +++ b/packages/arcgis-rest-portal/test/groups/add-users.test.ts @@ -0,0 +1,210 @@ +/* Copyright (c) 2019 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { + addGroupUsers, + IAddGroupUsersOptions +} from "../../src/groups/add-users"; +import { UserSession } from "@esri/arcgis-rest-auth"; +import { encodeParam } from "@esri/arcgis-rest-request"; +import { TOMORROW } from "@esri/arcgis-rest-auth/test/utils"; +import * as fetchMock from "fetch-mock"; + +function createUsernames(start: number, end: number): string[] { + const usernames = []; + + for (let i = start; i < end; i++) { + usernames.push(`username${i}`); + } + + return usernames; +} + +describe("add-users", () => { + const MOCK_AUTH = new UserSession({ + clientId: "clientId", + redirectUri: "https://example-app.com/redirect-uri", + token: "fake-token", + tokenExpires: TOMORROW, + refreshToken: "refreshToken", + refreshTokenExpires: TOMORROW, + refreshTokenTTL: 1440, + username: "casey", + password: "123456", + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }); + + afterEach(fetchMock.restore); + + it("should send multiple requests for a long user array", done => { + const requests = [createUsernames(0, 25), createUsernames(25, 35)]; + + const responses = [ + { notAdded: ["username1"] }, + { notAdded: ["username30"] } + ]; + + fetchMock.post("*", (url, options) => { + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/group-id/addUsers" + ); + expect(options.method).toBe("POST"); + expect(options.body).toContain(encodeParam("f", "json")); + expect(options.body).toContain(encodeParam("token", "fake-token")); + expect(options.body).toContain( + encodeParam("users", requests.shift().join(",")) + ); + + return responses.shift(); + }); + + const params = { + id: "group-id", + users: createUsernames(0, 35), + authentication: MOCK_AUTH + }; + + addGroupUsers(params) + .then(result => { + expect(requests.length).toEqual(0); + expect(responses.length).toEqual(0); + expect(result.notAdded).toEqual(["username1", "username30"]); + expect(result.errors).toBeUndefined(); + done(); + }) + .catch(error => fail(error)); + }); + + it("should send multiple requests for a long admin array", done => { + const requests = [createUsernames(0, 25), createUsernames(25, 35)]; + + const responses = [ + { notAdded: ["username1"] }, + { notAdded: ["username30"] } + ]; + + fetchMock.post("*", (url, options) => { + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/group-id/addUsers" + ); + expect(options.method).toBe("POST"); + expect(options.body).toContain(encodeParam("f", "json")); + expect(options.body).toContain(encodeParam("token", "fake-token")); + expect(options.body).toContain( + encodeParam("admins", requests.shift().join(",")) + ); + + return responses.shift(); + }); + + const params = { + id: "group-id", + admins: createUsernames(0, 35), + authentication: MOCK_AUTH + }; + + addGroupUsers(params) + .then(result => { + expect(requests.length).toEqual(0); + expect(responses.length).toEqual(0); + expect(result.notAdded).toEqual(["username1", "username30"]); + expect(result.errors).toBeUndefined(); + done(); + }) + .catch(error => fail(error)); + }); + + it("should send separate requests for users and admins", done => { + const requests = [ + encodeParam("users", ["username1", "username2"]), + encodeParam("admins", ["username3"]) + ]; + + fetchMock.post("*", (url, options) => { + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/group-id/addUsers" + ); + expect(options.method).toBe("POST"); + expect(options.body).toContain(encodeParam("f", "json")); + expect(options.body).toContain(encodeParam("token", "fake-token")); + expect(options.body).toContain(requests.shift()); + + return { notAdded: [] }; + }); + + const params = { + id: "group-id", + users: ["username1", "username2"], + admins: ["username3"], + authentication: MOCK_AUTH + }; + + addGroupUsers(params) + .then(result => { + expect(requests.length).toEqual(0); + expect(result.notAdded).toEqual([]); + expect(result.errors).toBeUndefined(); + done(); + }) + .catch(error => fail(error)); + }); + + it("should return request failure", done => { + const responses = [ + { notAdded: [] }, + { + error: { + code: 400, + messageCode: "ORG_3100", + message: "error message" + } + }, + { notAdded: ["username30"] } + ]; + + fetchMock.post("*", () => responses.shift()); + + const params = { + id: "group-id", + users: createUsernames(0, 30), + admins: createUsernames(30, 35), + authentication: MOCK_AUTH + }; + + addGroupUsers(params) + .then(result => { + expect(responses.length).toEqual(0); + expect(result.notAdded).toEqual(createUsernames(25, 31)); + expect(result.errors.length).toEqual(1); + + const error = result.errors[0]; + expect(error.url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/group-id/addUsers" + ); + expect(error.code).toEqual("ORG_3100"); + expect(error.originalMessage).toEqual("error message"); + + done(); + }) + .catch(error => fail(error)); + }); + + it("should not send any request for zero-length username array", done => { + const params: IAddGroupUsersOptions = { + id: "group-id", + users: [], + admins: [], + authentication: MOCK_AUTH + }; + + addGroupUsers(params) + .then(result => { + expect(fetchMock.called()).toEqual(false); + expect(result.notAdded).toEqual([]); + expect(result.errors).toBeUndefined(); + + done(); + }) + .catch(error => fail(error)); + }); +}); diff --git a/packages/arcgis-rest-portal/test/util/array.test.ts b/packages/arcgis-rest-portal/test/util/array.test.ts new file mode 100644 index 0000000000..3a66a2825a --- /dev/null +++ b/packages/arcgis-rest-portal/test/util/array.test.ts @@ -0,0 +1,30 @@ +/* Copyright (c) 2019 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { chunk } from "../../src/util/array"; + +describe("array.chunk", () => { + it("should chunk a long array", () => { + const input = [1, 2, 3, 4]; + const result = chunk(input, 3); + expect(result).toEqual([[1, 2, 3], [4]]); + }); + + it("should chunk an array with the length proportional to the chunk size", () => { + const input = [1, 2, 3, 4]; + const result = chunk(input, 2); + expect(result).toEqual([[1, 2], [3, 4]]); + }); + + it("should not chunk a short array", () => { + const input = [1, 2, 3, 4]; + const result = chunk(input, 5); + expect(result).toEqual([[1, 2, 3, 4]]); + }); + + it("should not chunk a zero-length array", () => { + const input: number[] = []; + const result = chunk(input, 5); + expect(result).toEqual([]); + }); +}); From 9cb90e6a557fea68d085b7907e3d30c401670630 Mon Sep 17 00:00:00 2001 From: Haoliang Yu Date: Mon, 17 Jun 2019 15:39:35 -0400 Subject: [PATCH 2/3] fix test coverage --- .../test/groups/add-users.test.ts | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/packages/arcgis-rest-portal/test/groups/add-users.test.ts b/packages/arcgis-rest-portal/test/groups/add-users.test.ts index 67648ebb01..35facd1eb8 100644 --- a/packages/arcgis-rest-portal/test/groups/add-users.test.ts +++ b/packages/arcgis-rest-portal/test/groups/add-users.test.ts @@ -151,15 +151,22 @@ describe("add-users", () => { it("should return request failure", done => { const responses = [ - { notAdded: [] }, + { notAdded: ["username2"] }, { error: { code: 400, messageCode: "ORG_3100", - message: "error message" + message: "error message for add-user request" } }, - { notAdded: ["username30"] } + { notAdded: ["username30"] }, + { + error: { + code: 400, + messageCode: "ORG_3200", + message: "error message for add-admin request" + } + } ]; fetchMock.post("*", () => responses.shift()); @@ -167,22 +174,46 @@ describe("add-users", () => { const params = { id: "group-id", users: createUsernames(0, 30), - admins: createUsernames(30, 35), + admins: createUsernames(30, 60), authentication: MOCK_AUTH }; addGroupUsers(params) .then(result => { expect(responses.length).toEqual(0); - expect(result.notAdded).toEqual(createUsernames(25, 31)); - expect(result.errors.length).toEqual(1); - const error = result.errors[0]; - expect(error.url).toEqual( + const expectedNotAdded = ["username2"] + .concat(createUsernames(25, 31)) + .concat(createUsernames(55, 60)); + expect(result.notAdded).toEqual(expectedNotAdded); + + expect(result.errors.length).toEqual(2); + + const errorA = result.errors[0]; + expect(errorA.url).toEqual( "https://myorg.maps.arcgis.com/sharing/rest/community/groups/group-id/addUsers" ); - expect(error.code).toEqual("ORG_3100"); - expect(error.originalMessage).toEqual("error message"); + expect(errorA.code).toEqual("ORG_3100"); + expect(errorA.originalMessage).toEqual( + "error message for add-user request" + ); + + const errorAOptions: any = errorA.options; + expect(errorAOptions.users).toEqual(createUsernames(25, 30)); + expect(errorAOptions.admins).toBeUndefined(); + + const errorB = result.errors[1]; + expect(errorB.url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/group-id/addUsers" + ); + expect(errorB.code).toEqual("ORG_3200"); + expect(errorB.originalMessage).toEqual( + "error message for add-admin request" + ); + + const errorBOptions: any = errorB.options; + expect(errorBOptions.users).toBeUndefined(); + expect(errorBOptions.admins).toEqual(createUsernames(55, 60)); done(); }) From 5acf7db1bdac61e3128a9e00430d7e4d393f75da Mon Sep 17 00:00:00 2001 From: Haoliang Yu Date: Mon, 17 Jun 2019 16:34:40 -0400 Subject: [PATCH 3/3] update per comment --- packages/arcgis-rest-portal/src/groups/add-users.ts | 3 +-- packages/arcgis-rest-portal/test/groups/add-users.test.ts | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/arcgis-rest-portal/src/groups/add-users.ts b/packages/arcgis-rest-portal/src/groups/add-users.ts index 9c84ff6865..9d2f1936ee 100644 --- a/packages/arcgis-rest-portal/src/groups/add-users.ts +++ b/packages/arcgis-rest-portal/src/groups/add-users.ts @@ -107,14 +107,13 @@ function _generateRequestOptions( }); } +// this request is safe since the request error will be handled function _sendSafeRequest( url: string, requestOptions: IAddGroupUsersOptions ): Promise { return request(url, requestOptions).catch(error => { return { - // the request should either add users or admins, not both - notAdded: requestOptions.users || requestOptions.admins, errors: [error] }; }); diff --git a/packages/arcgis-rest-portal/test/groups/add-users.test.ts b/packages/arcgis-rest-portal/test/groups/add-users.test.ts index 35facd1eb8..891655b8b3 100644 --- a/packages/arcgis-rest-portal/test/groups/add-users.test.ts +++ b/packages/arcgis-rest-portal/test/groups/add-users.test.ts @@ -182,9 +182,7 @@ describe("add-users", () => { .then(result => { expect(responses.length).toEqual(0); - const expectedNotAdded = ["username2"] - .concat(createUsernames(25, 31)) - .concat(createUsernames(55, 60)); + const expectedNotAdded = ["username2", "username30"]; expect(result.notAdded).toEqual(expectedNotAdded); expect(result.errors.length).toEqual(2);