From 6c5526a21dc6a9a5cf2ff690bfbfb3a1aed2a033 Mon Sep 17 00:00:00 2001 From: Michael Webber Date: Tue, 25 Jul 2023 11:58:53 -0400 Subject: [PATCH 1/2] Initialization of API Module for Dropbox Authentication and manager tests passing One request to Dropbox API for folders --- api-module-library/dropbox/.eslintrc.json | 3 + api-module-library/dropbox/.gitignore | 24 +++ api-module-library/dropbox/CHANGELOG.md | 4 + api-module-library/dropbox/LICENSE.md | 9 + api-module-library/dropbox/README.md | 5 + api-module-library/dropbox/api.js | 99 ++++++++++ api-module-library/dropbox/defaultConfig.json | 9 + api-module-library/dropbox/index.js | 13 ++ api-module-library/dropbox/jest-setup.js | 2 + api-module-library/dropbox/jest-teardown.js | 2 + api-module-library/dropbox/jest.config.js | 21 +++ api-module-library/dropbox/manager.js | 177 ++++++++++++++++++ .../dropbox/models/credential.js | 18 ++ api-module-library/dropbox/models/entity.js | 8 + api-module-library/dropbox/package.json | 25 +++ api-module-library/dropbox/tests/api.test.js | 47 +++++ .../dropbox/tests/manager.test.js | 79 ++++++++ 17 files changed, 545 insertions(+) create mode 100644 api-module-library/dropbox/.eslintrc.json create mode 100644 api-module-library/dropbox/.gitignore create mode 100644 api-module-library/dropbox/CHANGELOG.md create mode 100644 api-module-library/dropbox/LICENSE.md create mode 100644 api-module-library/dropbox/README.md create mode 100644 api-module-library/dropbox/api.js create mode 100644 api-module-library/dropbox/defaultConfig.json create mode 100644 api-module-library/dropbox/index.js create mode 100644 api-module-library/dropbox/jest-setup.js create mode 100644 api-module-library/dropbox/jest-teardown.js create mode 100644 api-module-library/dropbox/jest.config.js create mode 100644 api-module-library/dropbox/manager.js create mode 100644 api-module-library/dropbox/models/credential.js create mode 100644 api-module-library/dropbox/models/entity.js create mode 100644 api-module-library/dropbox/package.json create mode 100644 api-module-library/dropbox/tests/api.test.js create mode 100644 api-module-library/dropbox/tests/manager.test.js diff --git a/api-module-library/dropbox/.eslintrc.json b/api-module-library/dropbox/.eslintrc.json new file mode 100644 index 000000000..642d4c7d1 --- /dev/null +++ b/api-module-library/dropbox/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@friggframework/eslint-config" +} diff --git a/api-module-library/dropbox/.gitignore b/api-module-library/dropbox/.gitignore new file mode 100644 index 000000000..4bdfb909b --- /dev/null +++ b/api-module-library/dropbox/.gitignore @@ -0,0 +1,24 @@ +# dependencies +**/node_modules + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# webstorm local config +.idea/ + +.env diff --git a/api-module-library/dropbox/CHANGELOG.md b/api-module-library/dropbox/CHANGELOG.md new file mode 100644 index 000000000..f67d14dfb --- /dev/null +++ b/api-module-library/dropbox/CHANGELOG.md @@ -0,0 +1,4 @@ +# v0.0.1 (Jul 24 2023) + +#### Generated +- Initialized from template diff --git a/api-module-library/dropbox/LICENSE.md b/api-module-library/dropbox/LICENSE.md new file mode 100644 index 000000000..0fe77e299 --- /dev/null +++ b/api-module-library/dropbox/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2023 Left Hook Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/api-module-library/dropbox/README.md b/api-module-library/dropbox/README.md new file mode 100644 index 000000000..0e1c9400b --- /dev/null +++ b/api-module-library/dropbox/README.md @@ -0,0 +1,5 @@ +# Dropbox + +This is the API Module for Dropbox that allows the [Frigg](https://friggframework.org) code to talk to the Dropbox API. + +Read more on the [Frigg documentation site](https://docs.friggframework.org/api-modules/list/dropbox diff --git a/api-module-library/dropbox/api.js b/api-module-library/dropbox/api.js new file mode 100644 index 000000000..37092ca15 --- /dev/null +++ b/api-module-library/dropbox/api.js @@ -0,0 +1,99 @@ +const { OAuth2Requester } = require('@friggframework/module-plugin'); +const { get } = require('@friggframework/assertions'); + +class Api extends OAuth2Requester { + constructor(params) { + super(params); + this.baseUrl = 'https://api.dropboxapi.com/2'; + this.URLs = { + me: '/openid/userinfo', + listFolders: '/files/list_folder', + }; + this.authorizationUri = encodeURI( + `https://www.dropbox.com/oauth2/authorize?response_type=code` + + `&token_access_type=offline` + + `&prompt=consent` + + `&scope=${this.scope}` + + `&client_id=${this.client_id}` + + `&redirect_uri=${this.redirect_uri}` + ); + this.tokenUri = 'https://api.dropboxapi.com/oauth2/token'; + } + + + + async getTokenFromCode(code) { + const params = new URLSearchParams(); + params.append('grant_type', 'authorization_code'); + params.append('client_id', this.client_id); + params.append('client_secret', this.client_secret); + params.append('redirect_uri', this.redirect_uri); + params.append('code', code); + const options = { + body: params, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + url: this.tokenUri, + }; + const response = await this._post(options, false); + await this.setTokens(response); + return response; + } + + async refreshAccessToken(refreshTokenObject) { + this.access_token = undefined; + const params = new URLSearchParams(); + params.append('grant_type', 'refresh_token'); + params.append('client_id', this.client_id); + params.append('client_secret', this.client_secret); + params.append('refresh_token', refreshTokenObject.refresh_token); + + const options = { + body: params, + url: this.tokenUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }; + const response = await this._post(options, false); + response.refresh_token = refreshTokenObject.refresh_token; + await this.setTokens(response); + } + + async getUserDetails() { + const options = { + url: this.baseUrl + this.URLs.me, + headers: { + 'Content-Type': 'application/json' + }, + body: {} + }; + return this._post(options); + } + async getTokenIdentity() { + const userDetails = await this.getUserDetails(); + return {identifier: userDetails.sub, name: `${userDetails.givenName} ${userDetails.familyName}`} + } + + async listFolders() { + const options = { + url: this.baseUrl + this.URLs.listFolders, + headers: { + 'Content-Type': 'application/json' + }, + body: { + path: '', + "include_deleted": false, + "include_has_explicit_shared_members": false, + "include_media_info": false, + "include_mounted_folders": true, + "include_non_downloadable_files": true, + "recursive": false + } + }; + return this._post(options); + } +} + +module.exports = { Api }; diff --git a/api-module-library/dropbox/defaultConfig.json b/api-module-library/dropbox/defaultConfig.json new file mode 100644 index 000000000..e74d3fd77 --- /dev/null +++ b/api-module-library/dropbox/defaultConfig.json @@ -0,0 +1,9 @@ +{ + "name": "dropbox", + "label": "Dropbox", + "productUrl": "", + "apiDocs": "", + "logoUrl": "https://friggframework.org/assets/img/dropbox.jpeg", + "categories": [], + "description": "Dropbox" +} diff --git a/api-module-library/dropbox/index.js b/api-module-library/dropbox/index.js new file mode 100644 index 000000000..e407eeb60 --- /dev/null +++ b/api-module-library/dropbox/index.js @@ -0,0 +1,13 @@ +const { Api } = require('./api'); +const { Credential } = require('./models/credential'); +const { Entity } = require('./models/entity'); +const ModuleManager = require('./manager'); +const Config = require('./defaultConfig'); + +module.exports = { + Api, + Credential, + Entity, + ModuleManager, + Config, +}; diff --git a/api-module-library/dropbox/jest-setup.js b/api-module-library/dropbox/jest-setup.js new file mode 100644 index 000000000..fddbb84ed --- /dev/null +++ b/api-module-library/dropbox/jest-setup.js @@ -0,0 +1,2 @@ +const { globalSetup } = require('@friggframework/test-environment'); +module.exports = globalSetup; diff --git a/api-module-library/dropbox/jest-teardown.js b/api-module-library/dropbox/jest-teardown.js new file mode 100644 index 000000000..69c882748 --- /dev/null +++ b/api-module-library/dropbox/jest-teardown.js @@ -0,0 +1,2 @@ +const { globalTeardown } = require('@friggframework/test-environment'); +module.exports = globalTeardown; diff --git a/api-module-library/dropbox/jest.config.js b/api-module-library/dropbox/jest.config.js new file mode 100644 index 000000000..cc9441f21 --- /dev/null +++ b/api-module-library/dropbox/jest.config.js @@ -0,0 +1,21 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // preset: '@friggframework/test-environment', + coverageThreshold: { + global: { + statements: 13, + branches: 0, + functions: 1, + lines: 13, + }, + }, + // A path to a module which exports an async function that is triggered once before all test suites + globalSetup: './jest-setup.js', + + // A path to a module which exports an async function that is triggered once after all test suites + globalTeardown: './jest-teardown.js', +}; diff --git a/api-module-library/dropbox/manager.js b/api-module-library/dropbox/manager.js new file mode 100644 index 000000000..90aa0e5b9 --- /dev/null +++ b/api-module-library/dropbox/manager.js @@ -0,0 +1,177 @@ +const { debug, flushDebugLog } = require('@friggframework/logs'); +const { get } = require('@friggframework/assertions'); +const { ModuleManager, ModuleConstants } = require('@friggframework/module-plugin'); +const { Api } = require('./api'); +const { Entity } = require('./models/entity'); +const { Credential } = require('./models/credential'); +const config = require('./defaultConfig.json') + +class Manager extends ModuleManager { + static Entity = Entity; + static Credential = Credential; + + constructor(params) { + super(params); + } + + static getName() { + return config.name; + } + + static async getInstance(params) { + let instance = new this(params); + const apiParams = { + client_id: process.env.DROPBOX_CLIENT_ID, + client_secret: process.env.DROPBOX_CLIENT_SECRET, + redirect_uri: `${process.env.REDIRECT_URI}/dropbox`, + scope: process.env.DROPBOX_SCOPE, + delegate: instance + }; + if (params.entityId) { + instance.entity = await Entity.findById(params.entityId); + instance.credential = await Credential.findById(instance.entity.credential); + } else if (params.credentialId) { + instance.credential = await Credential.findById(params.credentialId); + } + if (instance.credential) { + apiParams.access_token = instance.credential.access_token; + apiParams.refresh_token = instance.credential.refresh_token; + } + instance.api = await new Api(apiParams); + return instance; + } + + // Change to whatever your api uses to return identifying information + async testAuth() { + let validAuth = false; + try { + if (await this.api.getUserDetails()) validAuth = true; + } catch (e) { + flushDebugLog(e); + } + return validAuth; + } + + getAuthorizationRequirements(params) { + return { + url: this.api.getAuthorizationUri(), + type: ModuleConstants.authType.oauth2, + }; + } + + + async processAuthorizationCallback(params) { + const code = get(params.data, 'code'); + // For OAuth2, generate the token and store in this.credential and the DB + delete this.api.access_token; + await this.api.getTokenFromCode(code); + const entityDetails = await this.api.getTokenIdentity(); + await this.findOrCreateEntity(entityDetails); + + return { + credential_id: this.credential.id, + entity_id: this.entity.id, + type: Manager.getName(), + }; + } + + async findOrCreateEntity(params) { + const identifier = get(params, 'identifier'); + const name = get(params, 'name'); + + const search = await Entity.find({ + user: this.userId, + externalId: identifier, + }); + if (search.length === 0) { + // validate choices!!! + // create entity + const createObj = { + credential: this.credential.id, + user: this.userId, + name, + externalId: identifier, + }; + this.entity = await Entity.create(createObj); + } else if (search.length === 1) { + this.entity = search[0]; + } else { + debug( + 'Multiple entities found with the same external ID:', + identifier + ); + this.throwException(''); + } + } + + async receiveNotification(notifier, delegateString, object = null) { + if (!(notifier instanceof Api)) { + // no-op + } + else if (delegateString === this.api.DLGT_TOKEN_UPDATE) { + await this.updateOrCreateCredential(); + } + else if (delegateString === this.api.DLGT_TOKEN_DEAUTHORIZED) { + await this.deauthorize(); + } + else if (delegateString === this.api.DLGT_INVALID_AUTH) { + await this.markCredentialsInvalid(); + } + } + + async updateOrCreateCredential() { + const userDetails = await this.api.getTokenIdentity(); + const updatedToken = { + user: this.userId.toString(), + auth_is_valid: true, + }; + if (this.access_token) { updatedToken.access_token = this.access_token}; + if (this.refresh_token) { updatedToken.refresh_token = this.refresh_token}; + + // search for a credential for this user and identifier + // skip if we already have a credential + if (!this.credential){ + const credentialSearch = await Credential.find({ + identifier: userDetails.identifier + }) + if (credentialSearch.length > 1) { + debug(`Multiple credentials found with same identifier: ${userDetails.identifier}`); + this.throwException(`Multiple credentials found with same identifier: ${userDetails.identifier}`); + } + else if (credentialSearch === 1 && credentialSearch[0].user !== this.userId){ + debug(`A credential already exists with this identifier: ${userDetails.identifier}`); + this.throwException(`A credential already exists with this identifier: ${userDetails.identifier}`); + } + else if (credentialSearch === 1) { + // found exactly one credential with this identifier + this.credential = credentialSearch[0]; + } + else { + // found no credential with this identifier (match none for insert) + this.credential = {$exists: false}; + } + } + // update credential or create if none was found + this.credential = await Credential.findOneAndUpdate( + {_id: this.credential}, + {$set: updatedToken}, + {useFindAndModify: true, new: true, upsert: true} + ); + } + + async deauthorize() { + // wipe api connection + this.api = new Api(); + + // delete credentials from the database + const entity = await Entity.getByUserId(this.userId); + if (entity.credential) { + await Credential.delete(entity.credential); + entity.credential = undefined; + await entity.save(); + } + } +} + + +module.exports = Manager; diff --git a/api-module-library/dropbox/models/credential.js b/api-module-library/dropbox/models/credential.js new file mode 100644 index 000000000..83eb6e2b5 --- /dev/null +++ b/api-module-library/dropbox/models/credential.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose'); +const { Credential: Parent } = require('@friggframework/module-plugin'); +const schema = new mongoose.Schema({ + access_token: { + type: String, + trim: true, + lhEncrypt: true, + }, + refresh_token: { + type: String, + trim: true, + lhEncrypt: true, + }, + expires_at: { type: Number }, +}); +const name = 'DropboxCredential'; +const Credential = Parent.discriminators?.[name] || Parent.discriminator(name, schema); +module.exports = { Credential }; diff --git a/api-module-library/dropbox/models/entity.js b/api-module-library/dropbox/models/entity.js new file mode 100644 index 000000000..a5d89cb49 --- /dev/null +++ b/api-module-library/dropbox/models/entity.js @@ -0,0 +1,8 @@ +const mongoose = require('mongoose'); +const { Entity: Parent } = require('@friggframework/module-plugin'); + +const schema = new mongoose.Schema({}); +const name = 'DropboxEntity'; +const Entity = +Parent.discriminators?.[name] || Parent.discriminator(name, schema); +module.exports = { Entity }; diff --git a/api-module-library/dropbox/package.json b/api-module-library/dropbox/package.json new file mode 100644 index 000000000..1021fa9d5 --- /dev/null +++ b/api-module-library/dropbox/package.json @@ -0,0 +1,25 @@ +{ + "name": "@friggframework/api-module-dropbox", + "version": "0.0.1", + "prettier": "@friggframework/prettier-config", + "description": "", + "main": "index.js", + "scripts": { + "lint:fix": "prettier --write --loglevel error . && eslint . --fix", + "test": "jest" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "@friggframework/assertions": "^1.0.8", + "@friggframework/eslint-config": "^1.0.8", + "@friggframework/logs": "^1.0.9", + "@friggframework/module-plugin": "^1.0.27", + "@friggframework/test-environment": "^1.1.6", + "dotenv": "^16.3.1", + "eslint": "^8.45.0", + "jest": "^29.6.1", + "prettier": "^3.0.0", + "sinon": "^15.2.0" + } +} diff --git a/api-module-library/dropbox/tests/api.test.js b/api-module-library/dropbox/tests/api.test.js new file mode 100644 index 000000000..45e87e6af --- /dev/null +++ b/api-module-library/dropbox/tests/api.test.js @@ -0,0 +1,47 @@ +require('dotenv').config(); +const { Api } = require('../api'); +const Authenticator = require("@friggframework/test-environment/Authenticator"); + +describe('Dropbox API Tests', () => { + /* eslint-disable camelcase */ + const apiParams = { + client_id: process.env.DROPBOX_CLIENT_ID, + client_secret: process.env.DROPBOX_CLIENT_SECRET, + redirect_uri: `${process.env.REDIRECT_URI}/dropbox`, + scope: process.env.DROPBOX_SCOPE, + }; + /* eslint-enable camelcase */ + + const api = new Api(apiParams); + + beforeAll(async () => { + const url = api.getAuthorizationUri(); + const response = await Authenticator.oauth2(url); + await api.getTokenFromCode(response.data.code); + }); + describe('OAuth Flow Tests', () => { + it('Should generate an tokens', async () => { + expect(api.access_token).toBeTruthy(); + expect(api.refresh_token).toBeTruthy(); + }); + it('Should be able to refresh the token', async () => { + const oldToken = api.access_token; + await api.refreshAuth(); + expect(api.access_token).toBeTruthy(); + expect(api.access_token).not.toEqual(oldToken); + }); + }); + describe('Basic Identification Requests', () => { + it('Should retrieve information about the user', async () => { + const user = await api.getUserDetails(); + expect(user).toBeDefined(); + }); + }); + describe('File and Folder requests', () => { + it('Should retrieve folders', async () => { + const folders = await api.listFolders(); + expect(folders).toBeDefined() + expect(folders.entries).toBeInstanceOf(Array) + }); + }); +}); diff --git a/api-module-library/dropbox/tests/manager.test.js b/api-module-library/dropbox/tests/manager.test.js new file mode 100644 index 000000000..0e2f47a02 --- /dev/null +++ b/api-module-library/dropbox/tests/manager.test.js @@ -0,0 +1,79 @@ +require('dotenv').config(); +const Manager = require('../manager'); +const mongoose = require('mongoose'); +const Authenticator = require("@friggframework/test-environment/Authenticator"); +describe('Dropbox Manager Tests', () => { + let manager, authUrl; + beforeAll(async () => { + await mongoose.connect(process.env.MONGO_URI); + manager = await Manager.getInstance({ + userId: new mongoose.Types.ObjectId(), + }); + }); + + afterAll(async () => { + await Manager.Credential.deleteMany(); + await Manager.Entity.deleteMany(); + await mongoose.disconnect(); + }); + + describe('getAuthorizationRequirements() test', () => { + it('should return auth requirements', async () => { + const requirements = manager.getAuthorizationRequirements(); + expect(requirements).toBeDefined(); + expect(requirements.type).toEqual('oauth2'); + expect(requirements.url).toBeDefined(); + authUrl = requirements.url; + }); + }); + + describe('Authorization requests', () => { + let firstRes; + it('processAuthorizationCallback()', async () => { + const response = await Authenticator.oauth2(authUrl); + firstRes = await manager.processAuthorizationCallback({ + data: { + code: response.data.code, + }, + }); + expect(firstRes).toBeDefined(); + expect(firstRes.entity_id).toBeDefined(); + expect(firstRes.credential_id).toBeDefined(); + }); + it('retrieves existing entity on subsequent calls', async () =>{ + const response = await Authenticator.oauth2(authUrl); + const res = await manager.processAuthorizationCallback({ + data: { + code: response.data.code, + }, + }); + expect(res).toEqual(firstRes); + }); + it('get new token via refresh', async () => { + manager.api.access_token = 'foobar'; + const response = await manager.testAuth(); + expect(response).toBeTruthy(); + expect(manager.api.access_token).not.toEqual('foobar'); + }); + }); + describe('Test credential retrieval and manager instantiation', () => { + it('retrieve by entity id', async () => { + const newManager = await Manager.getInstance({ + userId: manager.userId, + entityId: manager.entity.id, + }); + expect(newManager).toBeDefined(); + expect(newManager.entity).toBeDefined(); + expect(newManager.credential).toBeDefined(); + }); + + it('retrieve by credential id', async () => { + const newManager = await Manager.getInstance({ + userId: manager.userId, + credentialId: manager.credential.id, + }); + expect(newManager).toBeDefined(); + expect(newManager.credential).toBeDefined(); + }); + }); +}); From 907fd02bfb8e034e4fd0d28fed5010f1a821fb85 Mon Sep 17 00:00:00 2001 From: Michael Webber Date: Wed, 26 Jul 2023 10:20:11 -0400 Subject: [PATCH 2/2] list shared folders methods --- api-module-library/dropbox/api.js | 47 +++++++++++++++++++- api-module-library/dropbox/tests/api.test.js | 12 ++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/api-module-library/dropbox/api.js b/api-module-library/dropbox/api.js index 37092ca15..28d60c139 100644 --- a/api-module-library/dropbox/api.js +++ b/api-module-library/dropbox/api.js @@ -8,6 +8,9 @@ class Api extends OAuth2Requester { this.URLs = { me: '/openid/userinfo', listFolders: '/files/list_folder', + listFoldersContinue: '/files/list_folder/continue', + listSharedFolders: '/sharing/list_folders', + listSharedFoldersContinue: '/sharing/list_folders/continue', }; this.authorizationUri = encodeURI( `https://www.dropbox.com/oauth2/authorize?response_type=code` + @@ -76,7 +79,7 @@ class Api extends OAuth2Requester { return {identifier: userDetails.sub, name: `${userDetails.givenName} ${userDetails.familyName}`} } - async listFolders() { + async listFolders(bodyOverride) { const options = { url: this.baseUrl + this.URLs.listFolders, headers: { @@ -89,7 +92,47 @@ class Api extends OAuth2Requester { "include_media_info": false, "include_mounted_folders": true, "include_non_downloadable_files": true, - "recursive": false + "recursive": false, + } + }; + options.body = {...options.body, ...bodyOverride} + return this._post(options); + } + + async listFoldersContinue(cursor) { + const options = { + url: this.baseUrl + this.URLs.listFoldersContinue, + headers: { + 'Content-Type': 'application/json' + }, + body: { + cursor + } + }; + return this._post(options); + } + + async listSharedFolders(bodyOverride) { + const options = { + url: this.baseUrl + this.URLs.listSharedFolders, + headers: { + 'Content-Type': 'application/json' + }, + body: { + } + }; + options.body = {...options.body, ...bodyOverride} + return this._post(options); + } + + async listSharedFoldersContinue(cursor) { + const options = { + url: this.baseUrl + this.URLs.listSharedFoldersContinue, + headers: { + 'Content-Type': 'application/json' + }, + body: { + cursor } }; return this._post(options); diff --git a/api-module-library/dropbox/tests/api.test.js b/api-module-library/dropbox/tests/api.test.js index 45e87e6af..538ee3c7b 100644 --- a/api-module-library/dropbox/tests/api.test.js +++ b/api-module-library/dropbox/tests/api.test.js @@ -40,8 +40,16 @@ describe('Dropbox API Tests', () => { describe('File and Folder requests', () => { it('Should retrieve folders', async () => { const folders = await api.listFolders(); - expect(folders).toBeDefined() - expect(folders.entries).toBeInstanceOf(Array) + expect(folders).toBeDefined(); + expect(folders.entries).toBeInstanceOf(Array); + }); + }); + + describe('Shared File and Folder requests', () => { + it('Should retrieve folders', async () => { + const folders = await api.listSharedFolders(); + expect(folders).toBeDefined(); + expect(folders.entries).toBeInstanceOf(Array); }); }); });