diff --git a/.eslintrc.js b/.eslintrc.js index 1c0a39d7..f6a61a11 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,24 +1,24 @@ module.exports = { - // "env": { - // "browser": true, - // "amd": true - // }, - "extends": "standard", - // "globals": { - // "Atomics": "readonly", - // "SharedArrayBuffer": "readonly" - // }, - // "parserOptions": { - // "ecmaFeatures": { - // "jsx": true - // }, - // "ecmaVersion": 2015, - // "sourceType": "module" - // }, - 'plugins': [ - 'standard', - 'promise' - ], - "rules": { - } -}; \ No newline at end of file + // "env": { + // "browser": true, + // "amd": true + // }, + extends: 'standard', + // "globals": { + // "Atomics": "readonly", + // "SharedArrayBuffer": "readonly" + // }, + // "parserOptions": { + // "ecmaFeatures": { + // "jsx": true + // }, + // "ecmaVersion": 2015, + // "sourceType": "module" + // }, + plugins: [ + 'standard', + 'promise' + ], + rules: { + } +} diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml new file mode 100644 index 00000000..5bb82fba --- /dev/null +++ b/.github/workflows/unit-test.yml @@ -0,0 +1,28 @@ +name: Unit Test & Reports +on: + pull_request: + push: +jobs: + build-test: + name: Build & Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 # checkout the repo + - uses: actions/setup-node@v3 + with: + node-version: '12.x' + registry-url: 'https://registry.npmjs.org' + - run: npm ci # install packages + - run: npm run test:unit:report:json # run tests (configured to use jest-junit reporter) + - name: Test Report + uses: dorny/test-reporter@v1 + if: success() || failure() # run this step even if previous step failed + with: + name: Mocha Unit test # Name of the check run which will be created + path: report.json # Path to test results + reporter: mocha-json # Format of test results + - name: Coverage report + uses: lucassabreu/comment-coverage-clover@main + with: + name: Unit test Coverage report + file: coverage/clover.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore index fb751e04..b6a96d1c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ jspm_packages/ mochawesome-report/ coverage/ test/utility/dataFiles/ +report.json # TypeScript v1 declaration files typings/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ae5321ad..5b786432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [v1.7.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.7.0) (2023-04-04) + - Feature + - Marketplace API support added. +## [v1.6.1](https://github.com/contentstack/contentstack-management-javascript/tree/v1.6.1) (2022-12-09) + - Bug Fix + - SSO get stack details Latest + +## [v1.6.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.6.0) (2022-10-19) + - Feature + - OAuth token refresh function added + +## [v1.5.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.5.0) (2022-08-10) + - Feature + - App creation, fetch and update + - App configuration + - App Installation and getting installation details + +## [v1.4.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.4.0) (2022-07-26) +- Bug Fix + - Delete: Set entry workflow and asset download + - Asset Download + - Set Entry workflow stages + - OAuth token authorisation + ## [v1.2.4](https://github.com/contentstack/contentstack-management-javascript/tree/v1.2.4) (2021-07-19) - Bug Fix - Form data upload timeout on retrying rate limit error issue resolved diff --git a/LICENSE b/LICENSE index 0d6b21e9..af16eb4a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012-2022 Contentstack +Copyright (c) 2012-2023 Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/app/authorization/index.js b/lib/app/authorization/index.js new file mode 100644 index 00000000..66e3cd60 --- /dev/null +++ b/lib/app/authorization/index.js @@ -0,0 +1,107 @@ +import cloneDeep from 'lodash/cloneDeep' +import error from '../../core/contentstackError' + +export function Authorization (http, data, params) { + http.defaults.versioningStrategy = undefined + this.params = params || {} + if (data) { + if (data.organization_uid) { + this.params = { + organization_uid: data.organization_uid + } + } + if (data.app_uid) { + this.urlPath = `/manifests/${data.app_uid}/authorizations` + /** + * @description List all user authorizations made to an authorized app under a particular organization + * @memberof Authorization + * @func findAll + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('manifest_uid').authorization().findAll() + * .then((response) => console.log(response)) + */ + this.findAll = async (param = {}) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) }, + params: { + ...cloneDeep(param) + } + } + + const response = await http.get(this.urlPath, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description Revoke all users tokens issued to an authorized app for the particular organization + * @memberof Authorization + * @func revokeAll + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('manifest_uid').authorization().revokeAll() + * .then((response) => console.log(response)) + */ + this.revokeAll = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.delete(this.urlPath, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description Revoke user token issued to an authorized app for the particular organization + * @memberof Authorization + * @func revoke + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('manifest_uid').authorization().revoke('authorization_uid') + * .then((response) => console.log(response)) + */ + this.revoke = async (authorizationUid) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.delete(`${this.urlPath}/${authorizationUid}`, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + } + } +} diff --git a/lib/app/hosting/deployment.js b/lib/app/hosting/deployment.js new file mode 100644 index 00000000..073d4dae --- /dev/null +++ b/lib/app/hosting/deployment.js @@ -0,0 +1,189 @@ +import cloneDeep from 'lodash/cloneDeep' +import ContentstackCollection from '../../contentstackCollection' +import error from '../../core/contentstackError' + +export function Deployment (http, data, params) { + http.defaults.versioningStrategy = undefined + + if (data && data.app_uid) { + this.params = params || {} + this.urlPath = `/manifests/${data.app_uid}/hosting/deployments` + if (data.organization_uid) { + this.params = { + organization_uid: data.organization_uid + } + } + if (data.data) { + Object.assign(this, cloneDeep(data.data)) + if (this.organization_uid) { + this.params = { + organization_uid: this.organization_uid + } + } + } + if (this.uid) { + this.urlPath = `/manifests/${data.app_uid}/hosting/deployments/${this.uid}` + /** + * @descriptionThe The GET deployment call is used to get all the details of an deployment of an app + * @memberof Deployment + * @func fetch + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().deployment('deployment_uid').fetch() + * .then((data) => {}) + */ + this.fetch = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.get(`${this.urlPath}`, headers) + if (response.data) { + const content = response.data.data + return new Deployment(http, { data: content, app_uid: data.app_uid }, this.params) + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @descriptionThe The list deployment logs call is used to list logs of a deployment. + * @memberof Deployment + * @func logs + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().deployment('deployment_uid').logs() + * .then((data) => {}) + */ + this.logs = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.get(`${this.urlPath}/logs`, headers) + if (response.data) { + return response.data.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @descriptionThe The create signed download url call is used to get the download url of the deployment source code. + * @memberof signedDownloadUrl + * @func logs + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().deployment('deployment_uid').signedDownloadUrl() + * .then((data) => {}) + */ + this.signedDownloadUrl = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.post(`${this.urlPath}/signedDownloadUrl`, {}, headers) + if (response.data) { + return response.data.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + } else { + /** + * @descriptionThe The create hosting deployments call is used to deploy the uploaded file in hosting + * @memberof Deployment + * @func create + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().deployment().create() + * .then((data) => {}) + */ + this.create = async ({ uploadUid, fileType, withAdvancedOptions }) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + if (withAdvancedOptions) { + headers.params = { + with_advanced_options: withAdvancedOptions + } + } + const response = await http.post(`${this.urlPath}`, { upload_uid: uploadUid, file_type: fileType }, headers) + if (response.data) { + const content = response.data.data + return new Deployment(http, { data: content, app_uid: data.app_uid }, this.params) + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @descriptionThe The list deployments call is used to get all the available deployments made for an app. + * @memberof Deployment + * @func findAll + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().deployment().create() + * .then((data) => {}) + */ + this.findAll = async (param = {}) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) }, + params: { ...cloneDeep(param) } + } + + const response = await http.get(`${this.urlPath}`, headers) + if (response.data) { + const content = response.data + const collection = new ContentstackCollection(response, http) + collection.items = DeploymentCollection(http, content, data.app_uid, this.params) + return collection + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + } + } +} + +export function DeploymentCollection (http, data, appUid, param) { + const obj = cloneDeep(data.data) || [] + return obj.map((content) => { + return new Deployment(http, { data: content, app_uid: appUid }, param) + }) +} diff --git a/lib/app/hosting/index.js b/lib/app/hosting/index.js new file mode 100644 index 00000000..4fb57e96 --- /dev/null +++ b/lib/app/hosting/index.js @@ -0,0 +1,186 @@ +import cloneDeep from 'lodash/cloneDeep' +import error from '../../core/contentstackError' +import { Deployment } from './deployment' + +export function Hosting (http, data, params) { + http.defaults.versioningStrategy = undefined + + this.params = params || {} + if (data && data.app_uid) { + this.urlPath = `/manifests/${data.app_uid}/hosting` + if (data.organization_uid) { + this.params = { + organization_uid: data.organization_uid + } + } + + /** + * @description The get hosting call is used to fetch to know whether the hosting is enabled or not. + * @memberof Hosting + * @func isEnable + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().isEnable() + * .then((data) => {}) + */ + this.isEnable = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.get(this.urlPath, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @description The toggle hosting call is used to enable the hosting of an app. + * @memberof Hosting + * @func enable + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().enable() + * .then((data) => {}) + */ + this.enable = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.patch(`${this.urlPath}/enable`, {}, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @description The toggle hosting call is used to disable the hosting of an app. + * @memberof Hosting + * @func disable + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().disable() + * .then((data) => {}) + */ + this.disable = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.patch(`${this.urlPath}/disable`, {}, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @descriptionThe create signed upload url call is used to create an signed upload url for the files in hosting. + * @memberof Hosting + * @func createUploadUrl + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().createUploadUrl() + * .then((data) => {}) + */ + this.createUploadUrl = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.post(`${this.urlPath}/signedUploadUrl`, { }, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @descriptionThe The GET latest live deployment call is used to get details of latest deployment of the source file. + * @memberof Hosting + * @func latestLiveDeployment + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().latestLiveDeployment() + * .then((data) => {}) + */ + this.latestLiveDeployment = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.get(`${this.urlPath}/latestLiveDeployment`, headers) + if (response.data) { + const content = response.data.data + return new Deployment(http, { data: content, app_uid: data.app_uid }, this.params) + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @description Create instance of Hosting deployment. + * @memberof Hosting + * @func deployment + * @returns {Deployment} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting().deployment() + * + */ + this.deployment = (uid = null) => { + const content = { app_uid: data.app_uid } + if (uid) { + content.data = { + uid + } + } + + return new Deployment(http, content, this.params) + } + } +} diff --git a/lib/app/index.js b/lib/app/index.js index 32670401..3cf11c43 100644 --- a/lib/app/index.js +++ b/lib/app/index.js @@ -1,10 +1,13 @@ import cloneDeep from 'lodash/cloneDeep' import error from '../core/contentstackError' import { create, deleteEntity, fetch, fetchAll, update } from '../entity' +import { Authorization } from './authorization' +import { Hosting } from './hosting' import { Installation } from './installation' + export function App (http, data) { http.defaults.versioningStrategy = undefined - this.urlPath = '/apps' + this.urlPath = '/manifests' this.params = {} if (data) { if (data.organization_uid) { @@ -19,10 +22,10 @@ export function App (http, data) { organization_uid: this.organization_uid } } - this.urlPath = `/apps/${this.uid}` + this.urlPath = `/manifests/${this.uid}` /** - * @description The update an app call is used to update the app details such as name, description, icon, and so on. + * @description The update manifest call is used to update the app details such as name, description, icon, and so on. * @memberof App * @func update * @returns {Promise} @@ -35,7 +38,7 @@ export function App (http, data) { * description: 'APP_DESCRIPTION', * target_type: 'stack'/'organization', * } - * const app = client.organization('organization_uid').app('app_uid') + * const app = client.organization('organization_uid').app('manifest_uid') * app = Object.assign(app, updateApp) * app.update() * .then((app) => console.log(app)) @@ -44,7 +47,7 @@ export function App (http, data) { this.update = update(http, undefined, this.params) /** - * @description The get app details call is used to fetch details of a particular app with its ID. + * @description The get manifest call is used to fetch details of a particular app with its ID. * @memberof App * @func fetch * @returns {Promise} @@ -53,14 +56,14 @@ export function App (http, data) { * import * as contentstack from '@contentstack/management' * const client = contentstack.client({ authtoken: 'TOKEN'}) * - * client.organization('organization_uid').app('app_uid').fetch() + * client.organization('organization_uid').app('manifest_uid').fetch() * .then((app) => console.log(app)) * */ this.fetch = fetch(http, 'data', this.params) /** - * @description The delete an app call is used to delete the app. + * @description The delete manifest call is used to delete the app. * @memberof App * @func delete * @returns {Promise} @@ -69,7 +72,7 @@ export function App (http, data) { * import * as contentstack from '@contentstack/management' * const client = contentstack.client({ authtoken: 'TOKEN'}) * - * client.organization('organization_uid').app('app_uid').delete() + * client.organization('organization_uid').app('manifest_uid').delete() * .then((response) => console.log(response)) */ this.delete = deleteEntity(http, false, this.params) @@ -84,7 +87,7 @@ export function App (http, data) { * import * as contentstack from '@contentstack/management' * const client = contentstack.client({ authtoken: 'TOKEN'}) * - * client.organization('organization_uid').app('app_uid').fetchOAuth() + * client.organization('organization_uid').app('manifest_uid').fetchOAuth() * .then((oAuthConfig) => console.log(oAuthConfig)) */ this.fetchOAuth = async (param = {}) => { @@ -127,7 +130,7 @@ export function App (http, data) { * scopes: ['scope1', 'scope2'] * } * } - * client.organization('organization_uid').app('app_uid').updateOAuth({ config }) + * client.organization('organization_uid').app('manifest_uid').updateOAuth({ config }) * .then((oAuthConfig) => console.log(oAuthConfig)) */ this.updateOAuth = async ({ config, param = {} }) => { @@ -150,6 +153,22 @@ export function App (http, data) { } } + /** + * @description The hosting will allow you get, update, deploy manifest. + * @memberof App + * @func updateOAuth + * @returns {Promise} + * @returns {Hosting} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').hosting() + */ + this.hosting = () => { + return new Hosting(http, { app_uid: this.uid }, this.params) + } + /** * @description The install call is used to initiate the installation of the app * @memberof App @@ -161,7 +180,7 @@ export function App (http, data) { * @example * import * as contentstack from '@contentstack/management' * const client = contentstack.client({ authtoken: 'TOKEN'}) - * client.organization('organization_uid').app('app_uid').install({ targetUid: 'STACK_API_KEY', targetType: 'stack' }) + * client.organization('organization_uid').app('manifest_uid').install({ targetUid: 'STACK_API_KEY', targetType: 'stack' }) * .then((installation) => console.log(installation)) */ this.install = async ({ targetUid, targetType }) => { @@ -191,21 +210,109 @@ export function App (http, data) { * @example * import * as contentstack from '@contentstack/management' * const client = contentstack.client({ authtoken: 'TOKEN'}) - * client.organization('organization_uid').app('app_uid').installation().findAll() + * client.organization('organization_uid').app('manifest_uid').installation().findAll() * .then((installations) => console.log(installations)) * * @example * import * as contentstack from '@contentstack/management' * const client = contentstack.client({ authtoken: 'TOKEN'}) - * client.organization('organization_uid').app('app_uid').installation('installation_uid').fetch() + * client.organization('organization_uid').app('manifest_uid').installation('installation_uid').fetch() * .then((installation) => console.log(installation)) */ this.installation = (uid = null) => { return new Installation(http, uid ? { data: { uid } } : { app_uid: this.uid }, this.params) } + /** + * @description The GET app requests of an app call is used to retrieve all requests of an app. + * @returns Promise + * @memberof App + * @func getRequests + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('app_uid').getRequests() + * .then((response) => console.log(response)) + * + */ + this.getRequests = async () => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.get(`${this.urlPath}/requests`, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description The App authorization allow to authorize app for specific scope. + * @returns Promise + * @param {string} param.responseType Desired grant type + * @param {string} param.clientId Client id of the app + * @param {string} param.redirectUri Redirect URL of the app + * @param {string} param.scope Scopes of the app + * @param {string} param.state Local state provided by the client + * + * @memberof App + * @func authorize + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('app_uid').authorize() + * .then((response) => console.log(response)) + * + */ + this.authorize = async ({ responseType, clientId, redirectUri, scope, state }) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + const content = { + response_type: responseType, + client_id: clientId, + redirect_uri: redirectUri, + scope: scope + } + if (state) { + content.state = state + } + const response = await http.post(`${this.urlPath}/authorize`, content, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description The Authorization will allow you to get all authorization, revoke specific or all authorization + * @memberof App + * @func authorization + * @returns {Authorization} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * client.organization('organization_uid').app('manifest_uid').authorization() + */ + this.authorization = () => { + return new Authorization(http, { app_uid: this.uid }, this.params) + } } else { /** - * @description The create an app call is used for creating a new app in your Contentstack organization. + * @description The create manifest call is used for creating a new app/manifest in your Contentstack organization. * @memberof App * @func create * @returns {Promise} @@ -236,7 +343,7 @@ export function App (http, data) { this.create = create({ http, params: this.params }) /** - * @description The get all apps call is used to fetch all the apps in your Contentstack organization. + * @description The get all manifest call is used to fetch all the apps in your Contentstack organization. * @memberof App * @func findAll * @returns {Promise>} @@ -250,6 +357,38 @@ export function App (http, data) { * */ this.findAll = fetchAll(http, AppCollection, this.params) + + /** + * @description To get the apps list of authorized apps for the particular organization + * @memberof Organization + * @func authorizedApps + * @param {number} skip - Offset for skipping content in the response. + * @param {number} limit - Limit on api response to provide content in list. + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.organization('organization_uid').authorizedApps({ skip: 10 }) + * .then((roles) => console.log(roles)) + * + */ + this.findAllAuthorized = async (param = {}) => { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + headers.params = { ...param } + try { + const response = await http.get(`/authorized-apps`, headers) + if (response.data) { + return response.data + } else { + return error(response) + } + } catch (err) { + return error(err) + } + } } } return this diff --git a/lib/app/installation/index.js b/lib/app/installation/index.js index 367dbdbb..5962ec67 100644 --- a/lib/app/installation/index.js +++ b/lib/app/installation/index.js @@ -94,7 +94,127 @@ export function Installation (http, data, params = {}) { } || {} const response = await http.get(`${this.urlPath}/configuration`, headers) if (response.data) { - return response.data.data + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description To update organization level app installation configuration. + * @memberof Installation + * @func setConfiguration + * @param {*} config Config that needs to be updated + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('app_uid').installation('installation_uid').setConfiguration({}) + * .then((response) => console.log(response)) + */ + this.setConfiguration = async (config) => { + try { + const headers = { + headers: { ...cloneDeep(params) } + } || {} + const response = await http.put(`${this.urlPath}/configuration`, config, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @description To fetch server side organization level config required for the app. + * @memberof Installation + * @func getServerConfig + * @param {*} param + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('app_uid').installation('installation_uid').serverConfig() + * .then((response) => console.log(response)) + */ + this.serverConfig = async (param = {}) => { + try { + const headers = { + headers: { ...cloneDeep(params) }, + params: { + ...cloneDeep(param) + } + } || {} + const response = await http.get(`${this.urlPath}/server-configuration`, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description To update server side organization level config required for the app. + * @memberof Installation + * @func setServerConfig + * @param {*} config Config that needs to be updated + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('app_uid').installation('installation_uid').setServerConfig({}) + * .then((response) => console.log(response)) + */ + this.setServerConfig = async (config) => { + try { + const headers = { + headers: { ...cloneDeep(params) } + } || {} + const response = await http.put(`${this.urlPath}/server-configuration`, config, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + + /** + * @description To fetch installation data of an app configuration. + * @memberof Installation + * @func installationData + * @returns {Promise} + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').app('app_uid').installation('installation_uid').installationData() + * .then((response) => console.log(response)) + */ + this.installationData = async () => { + try { + const headers = { + headers: { ...cloneDeep(params) } + } || {} + const response = await http.get(`${this.urlPath}/installationData`, headers) + if (response.data) { + return response.data } else { throw error(response) } @@ -104,7 +224,7 @@ export function Installation (http, data, params = {}) { } } else { if (data.app_uid) { - this.urlPath = `apps/${data.app_uid}/installations` + this.urlPath = `manifests/${data.app_uid}/installations` /** * @description The find installation call is used to retrieve all installations of your Contentstack organization. * @memberof Installation diff --git a/lib/app/request/index.js b/lib/app/request/index.js new file mode 100644 index 00000000..7372c739 --- /dev/null +++ b/lib/app/request/index.js @@ -0,0 +1,110 @@ +import cloneDeep from 'lodash/cloneDeep' +import error from '../../core/contentstackError' + +export function AppRequest (http, data, param) { + http.defaults.versioningStrategy = undefined + this.params = param || {} + + if (data) { + if (data.organization_uid) { + this.params = { + organization_uid: data.organization_uid + } + } + /** + * @description The Delete app request call is used to delete an app request of an app in target_uid. + * @param {string} requestUID The ID of the request to be deleted + * @returns Promise + * @memberof AppRequest + * @func delete + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').request().delete('request_uid`) + * .then((response) => console.log(response)) + * + */ + this.delete = async (requestUid) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.delete(`/requests/${requestUid}`, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description The Create call is used to create a app request for an app. + * @param {string} appUid The uid for the app for request + * @param {string} targetUid The uid of the target, on which the app will be installed + * @returns Promise + * @memberof AppRequest + * @func create + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').request().create({ appUid: 'app_uid', targetUid: 'target_uid' }) + * .then((response) => console.log(response)) + * + */ + this.create = async ({ appUid, targetUid }) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) } + } + + const response = await http.post(`/requests`, { app_uid: appUid, target_uid: targetUid }, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + /** + * @description The GET all app requests call is used to retrieve all requests of all apps in an organization. + * @param {object} param object for query params + * @returns Promise + * @memberof AppRequest + * @func findAll + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').request().findAll() + * .then((response) => console.log(response)) + * + */ + this.findAll = async (param = {}) => { + try { + const headers = { + headers: { ...cloneDeep(this.params) }, + params: { ...param } + } + + const response = await http.get(`/requests`, headers) + if (response.data) { + return response.data + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } + } +} diff --git a/lib/contentstackClient.js b/lib/contentstackClient.js index 27ae01e2..a83d0693 100644 --- a/lib/contentstackClient.js +++ b/lib/contentstackClient.js @@ -26,6 +26,8 @@ export default function contentstackClient ({ http }) { * */ function login (requestBody, params = {}) { + http.defaults.versioningStrategy = 'path' + return http.post('/user-session', { user: requestBody }, { params: params }) .then((response) => { if (response.data.user != null && response.data.user.authtoken != null) { @@ -51,6 +53,7 @@ export default function contentstackClient ({ http }) { * */ function getUser (params = {}) { + http.defaults.versioningStrategy = 'path' return http.get('/user', { params: params }) .then((response) => { return new User(http, response.data) @@ -94,6 +97,7 @@ export default function contentstackClient ({ http }) { * .then((stack) => console.log(stack)) */ function stack (params = {}) { + http.defaults.versioningStrategy = 'path' const stack = { ...cloneDeep(params) } return new Stack(http, { stack }) } @@ -121,6 +125,7 @@ export default function contentstackClient ({ http }) { * */ function organization (uid = null) { + http.defaults.versioningStrategy = 'path' return new Organization(http, uid !== null ? { organization: { uid: uid } } : null) } @@ -144,6 +149,7 @@ export default function contentstackClient ({ http }) { * .then((response) => console.log(response)) * */ function logout (authtoken) { + http.defaults.versioningStrategy = 'path' if (authtoken !== undefined) { return http.delete('/user-session', { headers: { diff --git a/lib/contentstackCollection.js b/lib/contentstackCollection.js index 1e022140..7be70a01 100644 --- a/lib/contentstackCollection.js +++ b/lib/contentstackCollection.js @@ -7,7 +7,10 @@ export default class ContentstackCollection { if (stackHeaders) { data.stackHeaders = stackHeaders } - this.items = wrapperCollection(http, data) + if (wrapperCollection) { + this.items = wrapperCollection(http, data) + } + if (data.schema !== undefined) { this.schema = data.schema } diff --git a/lib/core/contentstackError.js b/lib/core/contentstackError.js index a6efc71d..a962a937 100644 --- a/lib/core/contentstackError.js +++ b/lib/core/contentstackError.js @@ -30,9 +30,10 @@ export default function error (errorResponse) { } if (data) { - errorDetails.errorMessage = data.error_message || '' + errorDetails.errorMessage = data.error_message || data.message || '' errorDetails.errorCode = data.error_code || 0 errorDetails.errors = data.errors || {} + errorDetails.error = data.error || '' } var error = new Error() diff --git a/lib/core/contentstackHTTPClient.js b/lib/core/contentstackHTTPClient.js index 4175904e..1ad77e1a 100644 --- a/lib/core/contentstackHTTPClient.js +++ b/lib/core/contentstackHTTPClient.js @@ -1,8 +1,8 @@ +import axios from 'axios' import clonedeep from 'lodash/cloneDeep' import Qs from 'qs' -import axios from 'axios' -import { isHost } from './Util' import { ConcurrencyQueue } from './concurrency-queue' +import { isHost } from './Util' export default function contentstackHttpClient (options) { const defaultConfig = { @@ -70,7 +70,7 @@ export default function contentstackHttpClient (options) { delete params.query var qs = Qs.stringify(params, { arrayFormat: 'brackets' }) if (query) { - qs = qs + `&query=${encodeURI(JSON.stringify(query))}` + qs = qs + `&query=${encodeURIComponent(JSON.stringify(query))}` } params.query = query return qs diff --git a/lib/organization/index.js b/lib/organization/index.js index 00090b15..5d35409f 100644 --- a/lib/organization/index.js +++ b/lib/organization/index.js @@ -6,6 +6,7 @@ import { RoleCollection } from '../stack/roles' import { StackCollection } from '../stack' import { UserCollection } from '../user' import { App } from '../app' +import { AppRequest } from '../app/request' /** * Organization is the top-level entity in the hierarchy of Contentstack, consisting of stacks and stack resources, and users. Organization allows easy management of projects as well as users within the Organization. Read more about Organizations.. * @namespace Organization @@ -205,7 +206,7 @@ export function Organization (http, data) { * @description Market place application information * @memberof Organization * @func app - * @param {String} uid: App uid. + * @param {String} uid: The ID of the app that you want to fetch details of. * @returns {App} Instance of App * * @example @@ -218,6 +219,21 @@ export function Organization (http, data) { this.app = (uid = null) => { return new App(http, uid !== null ? { data: { uid, organization_uid: this.uid } } : { organization_uid: this.uid }) } + + /** + * @description The Create request call is used to create a app request for an app. + * @returns Request + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client({ authtoken: 'TOKEN'}) + * + * client.organization('organization_uid').appRequest() + * + */ + this.appRequest = () => { + return new AppRequest(http, { organization_uid: this.organization_uid }, this.params) + } } else { /** * @description The Get all organizations call lists all organizations related to the system user in the order that they were created. diff --git a/package.json b/package.json index 2b4617d7..619a2b5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/management", - "version": "1.6.1", + "version": "1.7.0", "description": "The Content Management API is used to manage the content of your Contentstack account", "main": "./dist/node/contentstack-management.js", "browser": "./dist/web/contentstack-management.js", @@ -32,6 +32,7 @@ "test": "npm run test:api && npm run test:unit", "test:api": "BABEL_ENV=test nyc --reporter=html --reporter=text mocha --require @babel/register ./test/test.js -t 30000 --reporter mochawesome --require babel-polyfill", "test:unit": "BABEL_ENV=test nyc --reporter=html --reporter=text mocha --require @babel/register ./test/unit/index.js -t 30000 --reporter mochawesome --require babel-polyfill", + "test:unit:report:json": "BABEL_ENV=test nyc --reporter=clover --reporter=text mocha --require @babel/register ./test/unit/index.js -t 30000 --reporter json --reporter-options output=report.json --require babel-polyfill", "test:typescript": "jest --testPathPattern=test/typescript --config ./jest.config.js --coverage", "test:debug": "BABEL_ENV=test mocha debug --require @babel/register ./test", "lint": "eslint lib test", diff --git a/test/api/app-delete-test.js b/test/api/app-delete-test.js new file mode 100644 index 00000000..cd9a6648 --- /dev/null +++ b/test/api/app-delete-test.js @@ -0,0 +1,38 @@ +import dotenv from 'dotenv' +import { describe, it, setup } from 'mocha' +import { jsonReader } from '../utility/fileOperations/readwrite' +import { contentstackClient } from '../utility/ContentstackClient.js' +import { expect } from 'chai' + +dotenv.config() + +let apps = {} +let installation = {} +const orgID = process.env.ORGANIZATION +let client = {} + +describe('Apps api Test', () => { + setup(() => { + const user = jsonReader('loggedinuser.json') + client = contentstackClient(user.authtoken) + apps = jsonReader('apps.json') + installation = jsonReader('installation.json') + }) + + it('Uninstall installation test', done => { + client.organization(orgID).app(apps.uid).installation(installation.uid).uninstall() + .then((installation) => { + expect(installation).to.deep.equal({}) + done() + }).catch(done) + }) + + it('Delete app test', done => { + client.organization(orgID).app(apps.uid).delete() + .then((appResponse) => { + expect(appResponse).to.deep.equal({}) + done() + }) + .catch(done) + }) +}) diff --git a/test/api/app-request-test.js b/test/api/app-request-test.js new file mode 100644 index 00000000..e03bb114 --- /dev/null +++ b/test/api/app-request-test.js @@ -0,0 +1,52 @@ +import dotenv from 'dotenv' +import { describe, it, setup } from 'mocha' +import { jsonReader } from '../utility/fileOperations/readwrite' +import { contentstackClient } from '../utility/ContentstackClient.js' +import { expect } from 'chai' + +dotenv.config() + +let apps = {} +const orgID = process.env.ORGANIZATION +let client = {} +let stack = {} +let requestUID = '' +describe('Apps request api Test', () => { + setup(() => { + const user = jsonReader('loggedinuser.json') + client = contentstackClient(user.authtoken) + apps = jsonReader('apps.json') + stack = jsonReader('stack.json') + }) + + it('test create app request', done => { + client.organization(orgID).request() + .create({ appUid: apps.uid, targetUid: stack.api_key }) + .then((response) => { + requestUID = response.data.data.uid + expect(response.data).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + it('test get all request for oranization', done => { + client.organization(orgID).request() + .findAll() + .then((response) => { + expect(response.data).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + it('test delete app request', done => { + client.organization(orgID).request() + .delete(requestUID) + .then((response) => { + expect(response.data).to.not.equal(undefined) + done() + }) + .catch(done) + }) +}) diff --git a/test/api/app-test.js b/test/api/app-test.js index f4ac5122..b614e199 100644 --- a/test/api/app-test.js +++ b/test/api/app-test.js @@ -1,6 +1,6 @@ import dotenv from 'dotenv' import { describe, it, setup } from 'mocha' -import { jsonReader } from '../utility/fileOperations/readwrite' +import { jsonReader, jsonWrite } from '../utility/fileOperations/readwrite' import { contentstackClient } from '../utility/ContentstackClient.js' import { expect } from 'chai' @@ -16,7 +16,7 @@ const app = { description: 'My new test app', target_type: 'organization' } -const config = { redirect_uri: 'https://example.com/oauth/callback', app_token_config: { enabled: true, scopes: ['scim:manage'] }, user_token_config: { enabled: true, scopes: ['user:read', 'user:write', 'scim:manage'] } } +const config = { redirect_uri: 'https://example.com/oauth/callback', app_token_config: { enabled: true, scopes: ['scim:manage'] }, user_token_config: { enabled: true, scopes: ['user:read', 'user:write', 'scim:manage'], allow_pkce: true } } describe('Apps api Test', () => { setup(() => { @@ -39,10 +39,25 @@ describe('Apps api Test', () => { .catch(done) }) + it('Fetch all authorized apps test', done => { + client.organization(orgID).app().findAllAuthorized() + .then((apps) => { + for (const index in apps.data) { + const appObject = apps.data[index] + expect(appObject.name).to.not.equal(null) + expect(appObject.uid).to.not.equal(null) + expect(appObject.target_type).to.not.equal(null) + } + done() + }) + .catch(done) + }) + it('Create app test', done => { client.organization(orgID).app().create(app) .then((appResponse) => { appUid = appResponse.uid + jsonWrite(appResponse, 'apps.json') expect(appResponse.uid).to.not.equal(undefined) expect(appResponse.name).to.be.equal(app.name) expect(appResponse.description).to.be.equal(app.description) @@ -65,7 +80,7 @@ describe('Apps api Test', () => { }) it('Update app test', done => { - const updateApp = { name: 'Update my app Name' } + const updateApp = { name: 'Update my app name' } let appObject = client.organization(orgID).app(appUid) appObject = Object.assign(appObject, updateApp) appObject.update() @@ -106,6 +121,7 @@ describe('Apps api Test', () => { client.organization(orgID).app(appUid).install({ targetType: 'stack', targetUid: stack.api_key }) .then((installation) => { installationUid = installation.uid + jsonWrite(installation, 'installation.json') expect(installation.uid).to.not.equal(undefined) expect(installation.params.organization_uid).to.be.equal(orgID) expect(installation.urlPath).to.be.equal(`/installations/${installation.uid}`) @@ -117,10 +133,39 @@ describe('Apps api Test', () => { .catch(done) }) + it('Get installationData for installation test', done => { + client.organization(orgID).app(appUid).installation(installationUid).installationData() + .then((installation) => { + expect(installation).to.not.equal(null) + done() + }).catch(done) + }) + it('Get configuration for installation test', done => { client.organization(orgID).app(appUid).installation(installationUid).configuration() - .then((installation) => { - expect(installation).to.deep.equal({}) + .then((config) => { + expect(config).to.not.equal(null) + done() + }).catch(done) + }) + it('Set configuration for installation test', done => { + client.organization(orgID).app(appUid).installation(installationUid).setConfiguration({}) + .then((config) => { + expect(config.data).to.deep.equal({}) + done() + }).catch(done) + }) + it('Get server config for installation test', done => { + client.organization(orgID).app(appUid).installation(installationUid).serverConfig() + .then((config) => { + expect(config).to.not.equal(null) + done() + }).catch(done) + }) + it('Set server config for installation test', done => { + client.organization(orgID).app(appUid).installation(installationUid).serServerConfig({}) + .then((config) => { + expect(config.data).to.deep.equal({}) done() }).catch(done) }) @@ -137,19 +182,11 @@ describe('Apps api Test', () => { done() }).catch(done) }) - - it('Uninstall installation test', done => { - client.organization(orgID).app(appUid).installation(installationUid).uninstall() - .then((installation) => { - expect(installation).to.deep.equal({}) - done() - }).catch(done) - }) - - it('Delete app test', done => { - client.organization(orgID).app(appUid).delete() - .then((appResponse) => { - expect(appResponse).to.deep.equal({}) + it('test fetch app request', done => { + client.organization(orgID).app(appUid) + .getRequests() + .then((response) => { + expect(response.data).to.not.equal(undefined) done() }) .catch(done) diff --git a/test/api/authorization-test.js b/test/api/authorization-test.js new file mode 100644 index 00000000..a37e9319 --- /dev/null +++ b/test/api/authorization-test.js @@ -0,0 +1,46 @@ +import dotenv from 'dotenv' +import { describe, it, setup } from 'mocha' +import { jsonReader } from '../utility/fileOperations/readwrite' +import { contentstackClient } from '../utility/ContentstackClient.js' +import { expect } from 'chai' + +dotenv.config() + +const orgID = process.env.ORGANIZATION +let client = {} +let apps = {} + +describe('Apps api Test', () => { + setup(() => { + const user = jsonReader('loggedinuser.json') + client = contentstackClient(user.authtoken) + apps = jsonReader('apps.json') + }) + + it('fetch all authorization apps test', done => { + client.organization(orgID).app(apps.uid).authorization().findAll() + .then((response) => { + expect(response).to.not.equal(null) + done() + }) + .catch(done) + }) + + it('revoke all authorization apps test', done => { + client.organization(orgID).app(apps.uid).authorization().revokeAll() + .then((response) => { + expect(response).to.not.equal(null) + done() + }) + .catch(done) + }) + + it('revoke all authorization apps test', done => { + client.organization(orgID).app(apps.uid).authorization().revoke('uid') + .then((response) => { + expect(response).to.not.equal(null) + done() + }) + .catch(done) + }) +}) diff --git a/test/api/hosting-test.js b/test/api/hosting-test.js new file mode 100644 index 00000000..ccc32e23 --- /dev/null +++ b/test/api/hosting-test.js @@ -0,0 +1,139 @@ +import dotenv from 'dotenv' +import { describe, it, setup } from 'mocha' +import { jsonReader } from '../utility/fileOperations/readwrite' +import { contentstackClient } from '../utility/ContentstackClient.js' +import { expect } from 'chai' + +dotenv.config() + +let apps = {} +const orgID = process.env.ORGANIZATION +let client = {} +let uploadUid = '' +let deploymentUid = '' +describe('Apps hosting api Test', () => { + setup(() => { + const user = jsonReader('loggedinuser.json') + client = contentstackClient(user.authtoken) + apps = jsonReader('apps.json') + }) + + it('test get apps hosting details', done => { + makeHosting(apps.uid).isEnable() + .then((response) => { + expect(response.enabled).to.not.equal(false) + done() + }) + .catch(done) + }) + + it('test create upload url for apps hosting details', done => { + makeHosting(apps.uid).createUploadUrl() + .then((response) => { + uploadUid = response.upload_uid + expect(response.upload_uid).to.not.equal(undefined) + expect(response.form_fields).to.not.equal(undefined) + expect(response.upload_url).to.not.equal(undefined) + expect(response.expires_in).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + it('test deployment for signed upload app hosting', done => { + makeHosting(apps.uid).deployment().create({ uploadUid, fileType: 'SOURCE' }) + .then((response) => { + deploymentUid = response.uid + expect(response.deployment_number).to.not.equal(undefined) + expect(response.deployment_url).to.not.equal(undefined) + expect(response.environment).to.not.equal(undefined) + expect(response.uid).to.not.equal(undefined) + expect(response.urlPath).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + it('test deployment for signed upload app hosting', done => { + makeHosting(apps.uid).deployment().findAll() + .then((response) => { + response.items.forEach(deployment => { + expect(deployment.deployment_number).to.not.equal(undefined) + expect(deployment.deployment_url).to.not.equal(undefined) + expect(deployment.environment).to.not.equal(undefined) + expect(deployment.uid).to.not.equal(undefined) + expect(deployment.urlPath).to.not.equal(undefined) + }) + done() + }) + .catch(done) + }) + + it('test get deployment from uid for app hosting', done => { + makeHosting(apps.uid).deployment(deploymentUid).fetch() + .then((response) => { + expect(response.deployment_number).to.not.equal(undefined) + expect(response.deployment_url).to.not.equal(undefined) + expect(response.environment).to.not.equal(undefined) + expect(response.uid).to.not.equal(undefined) + expect(response.urlPath).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + it('test get deployment logs for app hosting', done => { + makeHosting(apps.uid).deployment(deploymentUid).logs() + .then((response) => { + for (const i in response) { + const deploymentLogs = response[i] + expect(deploymentLogs.message).to.not.equal(undefined) + expect(deploymentLogs.stage).to.not.equal(undefined) + expect(deploymentLogs.timestamp).to.not.equal(undefined) + } + done() + }) + .catch(done) + }) + + it('test get deployment signed download url for app hosting', done => { + makeHosting(apps.uid).deployment(deploymentUid).signedDownloadUrl() + .then((response) => { + expect(response.download_url).to.not.equal(undefined) + expect(response.expires_in).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + it('test latest live deployment for apps hosting', done => { + makeHosting(apps.uid).latestLiveDeployment() + .then((response) => { + expect(response).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + it('test enable apps hosting details', done => { + makeHosting(apps.uid).enable() + .then((response) => { + expect(response.enabled).to.not.equal(true) + done() + }) + .catch(done) + }) + + it('test disable apps hosting details', done => { + makeHosting(apps.uid).disable() + .then((response) => { + expect(response.enabled).to.not.equal(false) + done() + }) + .catch(done) + }) +}) + +function makeHosting (appUid) { + return client.organization(orgID).app(appUid).hosting() +} diff --git a/test/test.js b/test/test.js index de0f1a9a..58250cde 100644 --- a/test/test.js +++ b/test/test.js @@ -2,6 +2,10 @@ require('./api/user-test') require('./api/organization-test') require('./api/stack-test') require('./api/app-test') +require('./api/hosting-test') +require('./api/app-request-test') +require('./api/authorization-test') +require('./api/app-delete-test') require('./api/branch-test') require('./api/branchAlias-test') require('./api/locale-test') diff --git a/test/typescript/app-request.ts b/test/typescript/app-request.ts new file mode 100644 index 00000000..76bdf210 --- /dev/null +++ b/test/typescript/app-request.ts @@ -0,0 +1,39 @@ +import { expect } from 'chai' +import * as dotenv from 'dotenv' +import { AppRequest } from '../../types/app/request' +dotenv.config() +let requestUID = '' + +export function orgAppRequest (request: AppRequest) { + describe('Org App request api', () => { + test('test get all request for oranization', done => { + request + .findAll() + .then((response) => { + expect(response.data).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + test('test delete app request', done => { + request + .delete(requestUID) + .then((response) => { + expect(response.data).to.not.equal(undefined) + done() + }) + .catch(done) + }) + test('test create app request', done => { + request + .create({ appUid: '', targetUid: process.env.APIKEY as string}) + .then((response) => { + requestUID = response.data.data.uid + expect(response.data).to.not.equal(undefined) + done() + }) + .catch(done) + }) + }) +} diff --git a/test/typescript/app.ts b/test/typescript/app.ts index ceb4ad2a..a8e8a48f 100644 --- a/test/typescript/app.ts +++ b/test/typescript/app.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import * as dotenv from 'dotenv' -import { AppData, Apps } from '../../types/app' +import { AppData, AppOAuth, Apps } from '../../types/app' import { Organization } from '../../types/organization'; dotenv.config() let appUid = '' @@ -11,7 +11,7 @@ const app: AppData = { description: 'My new test app', target_type: 'organization', } -const config = { redirect_uri: 'https://example.com/oauth/callback', app_token_config: { enabled: true, scopes: ['scim:manage'] }, user_token_config: { enabled: true, scopes: ['user:read', 'user:write', 'scim:manage'] } } +const config: AppOAuth = { redirect_uri: 'https://example.com/oauth/callback', app_token_config: { enabled: true, scopes: ['scim:manage'] }, user_token_config: { enabled: true, scopes: ['user:read', 'user:write', 'scim:manage'], allow_pkce: true } } export function createApp(apps: Apps) { describe('App create', () => { @@ -19,6 +19,7 @@ export function createApp(apps: Apps) { apps.create(app) .then((appResponse) => { appUid = appResponse.uid + process.env.APP_UID = appResponse.uid expect(appResponse.uid).to.not.equal(undefined) expect(appResponse.name).to.be.equal(app.name) expect(appResponse.description).to.be.equal(app.description) @@ -55,6 +56,18 @@ export function fetchApp(organization: Organization) { done() }).catch(done) }) + test('Find all Authorized Apps', done => { + organization.app().findAllAuthorized() + .then((apps) => { + for (const index in apps.data) { + const appObject = apps.data[index] + expect(appObject.name).to.not.equal(null) + expect(appObject.uid).to.not.equal(null) + expect(appObject.target_type).to.not.equal(null) + } + done() + }).catch(done) + }) }) } @@ -145,6 +158,13 @@ export function installation(organization: Organization) { }).catch(done) }) + test('Get installation data for App installation', done => { + organization.app(appUid).installation(installationUid).installationData() + .then(() => { + done() + }).catch(done) + }) + test('Get Configuration for App installation', done => { organization.app(appUid).installation(installationUid).configuration() .then(() => { @@ -152,6 +172,26 @@ export function installation(organization: Organization) { }).catch(done) }) + test('Get Server Configuration for App installation', done => { + organization.app(appUid).installation(installationUid).serverConfig() + .then(() => { + done() + }).catch(done) + }) + + test('Set Configuration for App installation', done => { + organization.app(appUid).installation(installationUid).setConfiguration({}) + .then(() => { + done() + }).catch(done) + }) + test('Set Server Configuration for App installation', done => { + organization.app(appUid).installation(installationUid).setServerConfig({}) + .then(() => { + done() + }).catch(done) + }) + test('Uninstall App installation', done => { organization.app(appUid).installation(installationUid).uninstall() .then((installation) => { diff --git a/test/typescript/authorization.ts b/test/typescript/authorization.ts new file mode 100644 index 00000000..669265c3 --- /dev/null +++ b/test/typescript/authorization.ts @@ -0,0 +1,26 @@ +import { expect } from 'chai' +import * as dotenv from 'dotenv' +import { Authorization } from '../../types/app/authorization' +dotenv.config() + + +export function authorization (authorization: Authorization) { + describe('Authorization Apps api', () => { + test('test get all authorization for apps', done => { + authorization.findAll() + .then((response) => { + expect(response).to.not.equal(undefined) + done() + }) + .catch(done) + }) + test('test revoke all authorization for apps', done => { + authorization.revokeAll() + .then((response) => { + expect(response).to.not.equal(undefined) + done() + }) + .catch(done) + }) + }) +} \ No newline at end of file diff --git a/test/typescript/hosting.ts b/test/typescript/hosting.ts new file mode 100644 index 00000000..09f6ad5b --- /dev/null +++ b/test/typescript/hosting.ts @@ -0,0 +1,125 @@ +import { expect } from 'chai' +import * as dotenv from 'dotenv' +import { Hosting } from '../../types/app/hosting' +dotenv.config() +let uploadUid = '' +let deploymentUid = '' +export function hosting(hosting: Hosting) { + describe('Hosting api', () => { + test('create upload url', done => { + hosting.createUploadUrl() + .then((response)=> { + uploadUid = response.upload_uid + expect(response.upload_uid).to.not.equal(undefined) + expect(response.form_fields).to.not.equal(undefined) + expect(response.upload_url).to.not.equal(undefined) + expect(response.expires_in).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + test('isEnable hosting', done => { + hosting.isEnable() + .then((response) => { + expect(response.enabled).to.not.equal(false) + done() + }) + .catch(done) + }) + test('test latest live deployment for apps hosting', done => { + hosting.latestLiveDeployment() + .then((response) => { + expect(response).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + test('test enable apps hosting details', done => { + hosting.enable() + .then((response) => { + expect(response.enabled).to.not.equal(true) + done() + }) + .catch(done) + }) + + test('test disable apps hosting details', done => { + hosting.disable() + .then((response) => { + expect(response.enabled).to.not.equal(false) + done() + }) + .catch(done) + }) + }) +} + +export function deployment(hosting: Hosting) { + describe('deployment api', () => { + test('create deployment', done => { + hosting.deployment().create({ uploadUid, fileType: 'SOURCE' }) + .then((response) => { + deploymentUid = response.uid + expect(response.deployment_number).to.not.equal(undefined) + expect(response.deployment_url).to.not.equal(undefined) + expect(response.environment).to.not.equal(undefined) + expect(response.uid).to.not.equal(undefined) + expect(response.urlPath).to.not.equal(undefined) + done() + }) + .catch(done) + }) + test('find all deployment', done => { + hosting.deployment().findAll() + .then((response) => { + response.items.forEach(deployment => { + expect(deployment.deployment_number).to.not.equal(undefined) + expect(deployment.deployment_url).to.not.equal(undefined) + expect(deployment.environment).to.not.equal(undefined) + expect(deployment.uid).to.not.equal(undefined) + expect(deployment.urlPath).to.not.equal(undefined) + }) + done() + }) + .catch(done) + }) + test('test get deployment from uid for app hosting', done => { + hosting.deployment(deploymentUid).fetch() + .then((response) => { + expect(response.deployment_number).to.not.equal(undefined) + expect(response.deployment_url).to.not.equal(undefined) + expect(response.environment).to.not.equal(undefined) + expect(response.uid).to.not.equal(undefined) + expect(response.urlPath).to.not.equal(undefined) + done() + }) + .catch(done) + }) + + test('test get deployment logs for app hosting', done => { + hosting.deployment(deploymentUid).logs() + .then((response) => { + for (const i in response) { + const deploymentLogs = response[i] + expect(deploymentLogs.message).to.not.equal(undefined) + expect(deploymentLogs.stage).to.not.equal(undefined) + expect(deploymentLogs.timestamp).to.not.equal(undefined) + } + done() + }) + .catch(done) + }) + + test('test get deployment signed download url for app hosting', done => { + hosting.deployment(deploymentUid).signedDownloadUrl() + .then((response) => { + expect(response.download_url).to.not.equal(undefined) + expect(response.expires_in).to.not.equal(undefined) + done() + }) + .catch(done) + }) + }) +} \ No newline at end of file diff --git a/test/typescript/index.test.ts b/test/typescript/index.test.ts index a33b8580..824b5607 100644 --- a/test/typescript/index.test.ts +++ b/test/typescript/index.test.ts @@ -14,6 +14,9 @@ import { createEnvironment, deleteEnvironment, getEnvironment, updateEnvironment import { createDeliveryToken, deleteDeliveryToken, deliveryToken, queryDeliveryToken } from './deliveryToken'; import { createRole, findAllRole, getRole, getRoleUid, queryRole } from './role'; import { createApp, deleteApp, fetchApp, installation, updateApp, updateAuth } from './app'; +import { deployment, hosting } from './hosting'; +import { orgAppRequest } from './app-request'; +import { authorization } from './authorization'; dotenv.config() jest.setTimeout(10000); @@ -39,6 +42,11 @@ describe('Typescript API test', () => { updateApp(org) updateAuth(org) installation(org) + hosting(org.app(process.env.APP_UID as string).hosting()) + deployment(org.app(process.env.APP_UID as string).hosting()) + orgAppRequest(org.app(process.env.APP_UID as string).request()) + authorization(org.app(process.env.APP_UID as string).authorization()) + orgAppRequest(org.request()) deleteApp(org) const stack = client.stack({api_key: process.env.APIKEY as string}) diff --git a/test/unit/ContentstackHTTPClient-test.js b/test/unit/ContentstackHTTPClient-test.js index 7e3ea698..12e3a28d 100644 --- a/test/unit/ContentstackHTTPClient-test.js +++ b/test/unit/ContentstackHTTPClient-test.js @@ -103,7 +103,7 @@ describe('Contentstack HTTP Client', () => { defaultHostName: 'defaulthost' }) expect(axiosInstance.defaults.paramsSerializer({ skip: 1, limit: 1 })).to.be.equal('skip=1&limit=1') - expect(axiosInstance.defaults.paramsSerializer({ query: { title: 'title' }, limit: 1 })).to.be.equal('limit=1&query=%7B%22title%22:%22title%22%7D') + expect(axiosInstance.defaults.paramsSerializer({ query: { title: 'title' }, limit: 1 })).to.be.equal('limit=1&query=%7B%22title%22%3A%22title%22%7D') } catch (err) { expect(err.message).to.be.equal('Expected parameter accessToken') } diff --git a/test/unit/app-request-test.js b/test/unit/app-request-test.js new file mode 100644 index 00000000..8f3ead05 --- /dev/null +++ b/test/unit/app-request-test.js @@ -0,0 +1,130 @@ +import Axios from 'axios' +import { expect } from 'chai' +import { describe, it } from 'mocha' +import MockAdapter from 'axios-mock-adapter' +import { appMock } from './mock/objects' +import { AppRequest } from '../../lib/app/request' +import { requestMock } from './mock/request-mock' + +describe('Contentstack apps request test', () => { + it('test request without contents', () => { + const request = makeAppRequest() + expect(request.create).to.be.equal(undefined) + expect(request.delete).to.be.equal(undefined) + expect(request.findAll).to.be.equal(undefined) + }) + it('test request without app uid', () => { + const request = makeAppRequest({}) + expect(request.create).to.not.equal(undefined) + expect(request.delete).to.not.equal(undefined) + expect(request.findAll).to.not.equal(undefined) + }) + + it('test request with app uid and org uid', () => { + const organizationUid = 'ORG_UID' + const request = makeAppRequest({ organization_uid: organizationUid }) + expect(request.create).to.not.equal(undefined) + expect(request.delete).to.not.equal(undefined) + expect(request.findAll).to.not.equal(undefined) + expect(request.params.organization_uid).to.be.equal(organizationUid) + }) + + it('test find all request for organization', (done) => { + const mock = new MockAdapter(Axios) + mock.onGet(`/requests`).reply(200, { + data: [requestMock] + }) + + makeAppRequest({}) + .findAll() + .then((response) => { + expect(response.data).to.not.equal(undefined) + const requests = response.data + requests.forEach(request => { + checkRequest(request) + }) + done() + }) + .catch(done) + }) + it('test create request for app uid', (done) => { + const mock = new MockAdapter(Axios) + mock.onPost(`/requests`).reply(200, { + data: { ...requestMock } + }) + + makeAppRequest({}) + .create({ appUid: appMock.uid, targetUid: requestMock.target_uid }) + .then((response) => { + checkRequest(response.data) + done() + }) + .catch(done) + }) + it('test delete request for organization', (done) => { + const mock = new MockAdapter(Axios) + mock.onDelete(`/requests/${requestMock.uid}`).reply(200, { + + }) + + makeAppRequest({}) + .delete(requestMock.uid) + .then((request) => { + expect(request).to.not.equal(undefined) + done() + }) + .catch(done) + }) + it('test find all request for organization fail request', (done) => { + const mock = new MockAdapter(Axios) + mock.onGet(`/requests`).reply(400, { + + }) + + makeAppRequest({}) + .findAll() + .then(done) + .catch((error) => { + expect(error).to.not.equal(undefined) + done() + }) + }) + it('test create request for app uid fail request', (done) => { + const mock = new MockAdapter(Axios) + mock.onPost(`/requests`).reply(400, { + + }) + + makeAppRequest({}) + .create({ appUid: 'app_uid', targetUid: requestMock.target_uid }) + .then(done) + .catch((error) => { + expect(error).to.not.equal(undefined) + done() + }) + }) + it('test delete request for organization fail request', (done) => { + const mock = new MockAdapter(Axios) + mock.onDelete(`/requests/${requestMock.uid}`).reply(400, { + + }) + + makeAppRequest({}) + .delete(requestMock.uid) + .then(done) + .catch((error) => { + expect(error).to.not.equal(undefined) + done() + }) + }) +}) + +function makeAppRequest (data, param) { + return new AppRequest(Axios, data, param) +} + +function checkRequest (request) { + expect(request.organization_uid).to.be.equal(requestMock.organization_uid) + expect(request.target_uid).to.be.equal(requestMock.target_uid) + expect(request.uid).to.be.equal(requestMock.uid) +} diff --git a/test/unit/apps-test.js b/test/unit/apps-test.js index 1a63c403..6108f558 100644 --- a/test/unit/apps-test.js +++ b/test/unit/apps-test.js @@ -3,43 +3,77 @@ import { expect } from 'chai' import { App } from '../../lib/app' import { describe, it } from 'mocha' import MockAdapter from 'axios-mock-adapter' -import { appMock, noticeMock, oAuthMock } from './mock/objects' +import { appMock, installationMock, noticeMock, oAuthMock } from './mock/objects' +import { requestMock } from './mock/request-mock' describe('Contentstack apps test', () => { it('App without app uid', done => { const app = makeApp({}) - expect(app.urlPath).to.be.equal('/apps') + expect(app.urlPath).to.be.equal('/manifests') expect(app.create).to.not.equal(undefined) expect(app.findAll).to.not.equal(undefined) + expect(app.findAllAuthorized).to.not.equal(undefined) expect(app.fetch).to.be.equal(undefined) expect(app.update).to.be.equal(undefined) expect(app.delete).to.be.equal(undefined) expect(app.fetchOAuth).to.be.equal(undefined) expect(app.updateOAuth).to.be.equal(undefined) + expect(app.hosting).to.be.equal(undefined) expect(app.install).to.be.equal(undefined) expect(app.installation).to.be.equal(undefined) + expect(app.getRequests).to.be.equal(undefined) + expect(app.authorize).to.be.equal(undefined) done() }) it('App with app uid', done => { const uid = 'APP_UID' const app = makeApp({ data: { uid } }) - expect(app.urlPath).to.be.equal(`/apps/${uid}`) + expect(app.urlPath).to.be.equal(`/manifests/${uid}`) expect(app.create).to.be.equal(undefined) expect(app.findAll).to.be.equal(undefined) + expect(app.findAllAuthorized).to.be.equal(undefined) expect(app.fetch).to.not.equal(undefined) expect(app.update).to.not.equal(undefined) expect(app.delete).to.not.equal(undefined) expect(app.fetchOAuth).to.not.equal(undefined) expect(app.updateOAuth).to.not.equal(undefined) + expect(app.hosting).to.not.equal(undefined) expect(app.install).to.not.equal(undefined) expect(app.installation).to.not.equal(undefined) + expect(app.getRequests).to.not.equal(undefined) + expect(app.authorize).to.not.equal(undefined) + expect(app.hosting()).to.not.equal(undefined) + expect(app.installation()).to.not.equal(undefined) + expect(app.installation(uid)).to.not.equal(undefined) + expect(app.authorization()).to.not.equal(undefined) + done() + }) + + it('App with app uid', done => { + const uid = 'APP_UID' + const organizationUid = 'ORG_UID' + const app = makeApp({ data: { uid, organization_uid: organizationUid }, organization_uid: organizationUid }) + expect(app.urlPath).to.be.equal(`/manifests/${uid}`) + expect(app.create).to.be.equal(undefined) + expect(app.findAll).to.be.equal(undefined) + expect(app.findAllAuthorized).to.be.equal(undefined) + expect(app.fetch).to.not.equal(undefined) + expect(app.update).to.not.equal(undefined) + expect(app.delete).to.not.equal(undefined) + expect(app.fetchOAuth).to.not.equal(undefined) + expect(app.updateOAuth).to.not.equal(undefined) + expect(app.hosting).to.not.equal(undefined) + expect(app.install).to.not.equal(undefined) + expect(app.installation).to.not.equal(undefined) + expect(app.getRequests).to.not.equal(undefined) + expect(app.authorize).to.not.equal(undefined) done() }) it('Create app test', done => { const mock = new MockAdapter(Axios) - mock.onPost('/apps').reply(200, { + mock.onPost('/manifests').reply(200, { data: { ...appMock } @@ -57,7 +91,7 @@ describe('Contentstack apps test', () => { it('Update app test', done => { const mock = new MockAdapter(Axios) const uid = appMock.uid - mock.onPut(`/apps/${uid}`).reply(200, { + mock.onPut(`/manifests/${uid}`).reply(200, { data: { ...appMock } @@ -75,7 +109,7 @@ describe('Contentstack apps test', () => { it('Get app from UID test', done => { const mock = new MockAdapter(Axios) const uid = appMock.uid - mock.onGet(`/apps/${uid}`).reply(200, { + mock.onGet(`/manifests/${uid}`).reply(200, { data: { ...appMock } @@ -93,7 +127,7 @@ describe('Contentstack apps test', () => { it('Delete app from UID test', done => { const mock = new MockAdapter(Axios) const uid = appMock.uid - mock.onDelete(`/apps/${uid}`).reply(200, { + mock.onDelete(`/manifests/${uid}`).reply(200, { ...noticeMock }) @@ -109,7 +143,7 @@ describe('Contentstack apps test', () => { it('Get all apps in organization test', done => { const mock = new MockAdapter(Axios) - mock.onGet(`/apps`).reply(200, { + mock.onGet(`/manifests`).reply(200, { data: [appMock] }) @@ -122,10 +156,42 @@ describe('Contentstack apps test', () => { .catch(done) }) + it('Get all authorized apps in organization test', done => { + const content = { + visibility: 'private', + description: 'This is a test App.', + name: 'New App', + org_id: 'org_uid', + created_at: '2021-07-20T13:34:54.791Z', + updated_at: '2021-07-27T14:05:19.452Z', + id: 'id' + } + const mock = new MockAdapter(Axios) + mock.onGet(`/authorized-apps`).reply(200, { + data: [ + content + ] + }) + + makeApp({}) + .findAllAuthorized() + .then((response) => { + expect(response.data[0].visibility).to.be.equal(content.visibility) + expect(response.data[0].description).to.be.equal(content.description) + expect(response.data[0].name).to.be.equal(content.name) + expect(response.data[0].org_id).to.be.equal(content.org_id) + expect(response.data[0].created_at).to.be.equal(content.created_at) + expect(response.data[0].updated_at).to.be.equal(content.updated_at) + expect(response.data[0].id).to.be.equal(content.id) + done() + }) + .catch(done) + }) + it('Get oAuth configuration test', done => { const mock = new MockAdapter(Axios) const uid = appMock.uid - mock.onGet(`/apps/${uid}/oauth`).reply(200, { + mock.onGet(`/manifests/${uid}/oauth`).reply(200, { data: { ...oAuthMock } @@ -147,7 +213,7 @@ describe('Contentstack apps test', () => { it('Update oAuth configuration test', done => { const mock = new MockAdapter(Axios) const uid = appMock.uid - mock.onPut(`/apps/${uid}/oauth`).reply(200, { + mock.onPut(`/manifests/${uid}/oauth`).reply(200, { data: { ...oAuthMock } @@ -165,10 +231,126 @@ describe('Contentstack apps test', () => { }) .catch(done) }) + + it('app install test', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + mock.onPost(`/manifests/${uid}/install`).reply(200, { + data: { + ...installationMock + } + }) + const targetUid = 'target_uid' + const targetType = 'target_type' + makeApp({ data: { uid } }) + .install({ targetUid, targetType }) + .then((installation) => { + expect(installation.status).to.be.equal(installationMock.status) + expect(installation.manifest.name).to.be.equal(installationMock.manifest.name) + expect(installation.target.uid).to.be.equal(installationMock.target.uid) + expect(installation.organization_uid).to.be.equal(installationMock.organization_uid) + expect(installation.uid).to.be.equal(installationMock.uid) + done() + }) + .catch(done) + }) + + it('test fetch request for app uid', (done) => { + const uid = appMock.uid + const mock = new MockAdapter(Axios) + mock.onGet(`/manifests/${appMock.uid}/requests`).reply(200, { + data: { ...requestMock } + }) + + makeApp({ data: { uid } }) + .getRequests() + .then((response) => { + const request = response.data + expect(request.organization_uid).to.be.equal(requestMock.organization_uid) + expect(request.target_uid).to.be.equal(requestMock.target_uid) + expect(request.uid).to.be.equal(requestMock.uid) + done() + }) + .catch(done) + }) + it('test authorize app', (done) => { + const uid = appMock.uid + const mock = new MockAdapter(Axios) + mock.onPost(`/manifests/${appMock.uid}/authorize`).reply(200, { + data: { redirect_uri: 'uri' } + }) + + makeApp({ data: { uid } }) + .authorize({ responseType: 'type', clientId: 'id', redirectUri: 'uri', scope: 'scope' }) + .then((response) => { + expect(response.data.redirect_uri).to.be.equal('uri') + done() + }) + .catch(done) + }) + it('test authorize app fail request', (done) => { + const uid = appMock.uid + const mock = new MockAdapter(Axios) + mock.onPost(`/manifests/${appMock.uid}/authorize`).reply(400, { + + }) + + makeApp({ data: { uid } }) + .authorize({ state: 'state', responseType: 'type', clientId: 'id', redirectUri: 'uri', scope: 'scope' }) + .then(done) + .catch((error) => { + expect(error).to.not.equal(undefined) + done() + }) + }) + it('test fetch request for app uid fail request', (done) => { + const uid = appMock.uid + const mock = new MockAdapter(Axios) + mock.onGet(`/manifests/${appMock.uid}/requests`).reply(400, { + + }) + + makeApp({ data: { uid } }) + .getRequests() + .then(done) + .catch((error) => { + expect(error).to.not.equal(undefined) + done() + }) + }) + it('test authorize app fail request', (done) => { + const uid = appMock.uid + const mock = new MockAdapter(Axios) + mock.onPost(`/manifests/${appMock.uid}/requests`).reply(400, { + + }) + + makeApp({ data: { uid } }) + .getRequests() + .then(done) + .catch((error) => { + expect(error).to.not.equal(undefined) + done() + }) + }) + it('Get all authorized apps in organization fail request', done => { + const mock = new MockAdapter(Axios) + mock.onGet(`/authorized-apps`).reply(400, { + + }) + + makeApp({}) + .findAllAuthorized() + .then(done) + .catch((error) => { + expect(error).to.not.equal(undefined) + done() + }) + }) }) function checkApp (app) { - expect(app.urlPath).to.be.equal('/apps/UID') + expect(app.urlPath).to.be.equal('/manifests/UID') expect(app.created_at).to.be.equal('created_at_date') expect(app.updated_at).to.be.equal('updated_at_date') expect(app.uid).to.be.equal('UID') diff --git a/test/unit/authorization-test.js b/test/unit/authorization-test.js new file mode 100644 index 00000000..850694b8 --- /dev/null +++ b/test/unit/authorization-test.js @@ -0,0 +1,160 @@ +import Axios from 'axios' +import { expect } from 'chai' +import { describe, it } from 'mocha' +import MockAdapter from 'axios-mock-adapter' +import { Authorization } from '../../lib/app/authorization' + +const uid = 'APP_UID' +const orgUid = 'org_uid' +const authUid = 'AUTH_UID' + +describe('Contentstack apps authorization test', () => { + it('Authorization without content', () => { + const authorization = makeAuthorization() + expect(authorization.findAll).to.be.equal(undefined) + expect(authorization.revokeAll).to.be.equal(undefined) + expect(authorization.revoke).to.be.equal(undefined) + expect(authorization.params).to.not.equal(undefined) + }) + + it('Authorization without app uid', () => { + const authorization = makeAuthorization({}) + expect(authorization.findAll).to.be.equal(undefined) + expect(authorization.revokeAll).to.be.equal(undefined) + expect(authorization.revoke).to.be.equal(undefined) + expect(authorization.params).to.not.equal(undefined) + }) + it('Authorization with app uid', () => { + const authorization = makeAuthorization({ app_uid: uid }) + expect(authorization.urlPath).to.be.equal(`/manifests/${uid}/authorizations`) + expect(authorization.findAll).to.not.equal(undefined) + expect(authorization.revokeAll).to.not.equal(undefined) + expect(authorization.revoke).to.not.equal(undefined) + expect(authorization.params).to.not.equal(undefined) + }) + it('Authorization with app uid org uid as Param', () => { + const authorization = makeAuthorization({ app_uid: uid }, { organization_uid: orgUid }) + expect(authorization.urlPath).to.be.equal(`/manifests/${uid}/authorizations`) + expect(authorization.findAll).to.not.equal(undefined) + expect(authorization.revokeAll).to.not.equal(undefined) + expect(authorization.revoke).to.not.equal(undefined) + expect(authorization.params.organization_uid).to.be.equal(orgUid) + }) + it('Authorization with app uid, org uid as content', () => { + const authorization = makeAuthorization({ app_uid: uid, organization_uid: orgUid }) + expect(authorization.urlPath).to.be.equal(`/manifests/${uid}/authorizations`) + expect(authorization.findAll).to.not.equal(undefined) + expect(authorization.revokeAll).to.not.equal(undefined) + expect(authorization.revoke).to.not.equal(undefined) + expect(authorization.params.organization_uid).to.be.equal(orgUid) + }) + + it('test find all authorization for apps', done => { + const content = { + app_uid: 'app_uid', + organization_uid: 'org_uid', + scopes: [ + 'user.profile:read' + ], + created_at: '2021-09-09T05:03:10.473Z', + updated_at: '2021-09-09T05:03:10.473Z', + user: { + uid: 'uid' + } + } + const mock = new MockAdapter(Axios) + mock.onGet(`/manifests/${uid}/authorizations`).reply(200, { + data: [content] + }) + + makeAuthorization({ app_uid: uid }) + .findAll() + .then((response) => { + const result = response.data[0] + expect(result.app_uid).to.be.equal(content.app_uid) + expect(result.organization_uid).to.be.equal(content.organization_uid) + expect(result.scopes[0]).to.be.equal(content.scopes[0]) + expect(result.user.uid).to.be.equal(content.user.uid) + done() + }) + .catch(done) + }) + + it('test revoke all authorization for apps', done => { + const mock = new MockAdapter(Axios) + mock.onDelete(`/manifests/${uid}/authorizations`).reply(200, { + + }) + + makeAuthorization({ app_uid: uid }) + .revokeAll() + .then((response) => { + expect(response).to.not.equal(null) + done() + }) + .catch(done) + }) + + it('test revoke authorization for apps', done => { + const mock = new MockAdapter(Axios) + mock.onDelete(`/manifests/${uid}/authorizations/${authUid}`).reply(200, { + + }) + + makeAuthorization({ app_uid: uid }) + .revoke(authUid) + .then((response) => { + expect(response).to.not.equal(null) + done() + }) + .catch(done) + }) + + it('test find all authorization for apps fail request', done => { + const mock = new MockAdapter(Axios) + mock.onGet(`/manifests/${uid}/authorizations`).reply(400, { + }) + + makeAuthorization({ app_uid: uid }) + .findAll({}) + .then(done) + .catch((error) => { + expect(error).to.not.equal(null) + done() + }) + }) + + it('test revoke all authorization for apps fail request', done => { + const mock = new MockAdapter(Axios) + mock.onDelete(`/manifests/${uid}/authorizations`).reply(400, { + + }) + + makeAuthorization({ app_uid: uid }) + .revokeAll({}) + .then(done) + .catch((error) => { + expect(error).to.not.equal(null) + done() + }) + }) + + it('test revoke authorization for apps fail request', done => { + const mock = new MockAdapter(Axios) + mock.onDelete(`/manifests/${uid}/authorizations/${authUid}`).reply(400, { + + }) + + makeAuthorization({ app_uid: uid }) + .revoke(authUid) + .then(done) + .catch((error) => { + expect(error).to.not.equal(null) + done() + }) + }) +}) + +function makeAuthorization (data, param) { + return new Authorization(Axios, data, param) +} diff --git a/test/unit/deployment-test.js b/test/unit/deployment-test.js new file mode 100644 index 00000000..4add3d7e --- /dev/null +++ b/test/unit/deployment-test.js @@ -0,0 +1,214 @@ +import Axios from 'axios' +import { expect } from 'chai' +import { describe, it } from 'mocha' +import MockAdapter from 'axios-mock-adapter' +import { appMock } from './mock/objects' +import { latestLiveResponse, signedUrlResponse } from './mock/hosting-mock' +import { Deployment } from '../../lib/app/hosting/deployment' + +describe('Contentstack hosting test', () => { + it('test deployment without contents', done => { + const deployment = makeDeployment() + expect(deployment.create).to.be.equal(undefined) + expect(deployment.fetch).to.be.equal(undefined) + expect(deployment.findAll).to.be.equal(undefined) + expect(deployment.logs).to.be.equal(undefined) + expect(deployment.params).to.be.equal(undefined) + expect(deployment.signedDownloadUrl).to.be.equal(undefined) + expect(deployment.urlPath).to.be.equal(undefined) + done() + }) + it('test deployment without app uid', done => { + const deployment = makeDeployment({}) + expect(deployment.create).to.be.equal(undefined) + expect(deployment.fetch).to.be.equal(undefined) + expect(deployment.findAll).to.be.equal(undefined) + expect(deployment.logs).to.be.equal(undefined) + expect(deployment.params).to.be.equal(undefined) + expect(deployment.signedDownloadUrl).to.be.equal(undefined) + expect(deployment.urlPath).to.be.equal(undefined) + done() + }) + + it('test deployment with app uid', done => { + const uid = appMock.uid + const deployment = makeDeployment({ app_uid: uid }) + expect(deployment.create).to.not.equal(undefined) + expect(deployment.fetch).to.be.equal(undefined) + expect(deployment.findAll).to.not.equal(undefined) + expect(deployment.logs).to.be.equal(undefined) + expect(deployment.params.organization_uid).to.be.equal(undefined) + expect(deployment.signedDownloadUrl).to.be.equal(undefined) + expect(deployment.urlPath).to.be.equal(`/manifests/${uid}/hosting/deployments`) + done() + }) + + it('test deployment with app uid and org uid', done => { + const uid = appMock.uid + const organizationUid = appMock.organization_uid + const deployment = makeDeployment({ app_uid: uid, organization_uid: organizationUid }) + expect(deployment.create).to.not.equal(undefined) + expect(deployment.fetch).to.be.equal(undefined) + expect(deployment.findAll).to.not.equal(undefined) + expect(deployment.logs).to.be.equal(undefined) + expect(deployment.params.organization_uid).to.not.equal(undefined) + expect(deployment.signedDownloadUrl).to.be.equal(undefined) + expect(deployment.urlPath).to.be.equal(`/manifests/${uid}/hosting/deployments`) + done() + }) + + it('test deployment with deployment uid', done => { + const appUid = appMock.uid + const organizationUid = appMock.organization_uid + const uid = 'Deployment_uid' + const deployment = makeDeployment({ app_uid: appUid, organization_uid: organizationUid, data: { uid } }) + expect(deployment.create).to.be.equal(undefined) + expect(deployment.fetch).to.not.equal(undefined) + expect(deployment.findAll).to.be.equal(undefined) + expect(deployment.logs).to.not.equal(undefined) + expect(deployment.params.organization_uid).to.not.equal(undefined) + expect(deployment.signedDownloadUrl).to.not.equal(undefined) + expect(deployment.urlPath).to.be.equal(`/manifests/${appUid}/hosting/deployments/${uid}`) + done() + }) + + it('test get all deployment for hosting', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + const organizationUid = appMock.organization_uid + mock.onGet(`manifests/${uid}/hosting/deployments`).reply(200, { + data: [latestLiveResponse.data] + }) + + makeDeployment({ app_uid: uid, organization_uid: organizationUid }) + .findAll() + .then((deployments) => { + deployments.items.forEach(deployment => { + checkDeployment(deployment) + }) + done() + }) + .catch(done) + }) + + it('test create deployment from signed url', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + const uploadUid = signedUrlResponse.data.upload_uid + const fileType = 'fileType' + const organizationUid = appMock.organization_uid + mock.onPost(`manifests/${uid}/hosting/deployments`).reply(200, { + data: { ...latestLiveResponse.data } + }) + + makeDeployment({ app_uid: uid, organization_uid: organizationUid }) + .create({ uploadUid, fileType }) + .then((deployment) => { + checkDeployment(deployment) + done() + }) + .catch(done) + }) + it('test create deployment from signed url with advance options', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + const uploadUid = signedUrlResponse.data.upload_uid + const fileType = 'fileType' + const organizationUid = appMock.organization_uid + mock.onPost(`manifests/${uid}/hosting/deployments`).reply(200, { + data: { ...latestLiveResponse.data } + }) + + makeDeployment({ app_uid: uid, organization_uid: organizationUid }) + .create({ uploadUid, fileType, withAdvancedOptions: true }) + .then((deployment) => { + checkDeployment(deployment) + done() + }) + .catch(done) + }) + + it('test get deployment from uid', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + const organizationUid = appMock.organization_uid + const deploymentUid = latestLiveResponse.data.uid + mock.onGet(`manifests/${uid}/hosting/deployments/${deploymentUid}`).reply(200, { + data: { ...latestLiveResponse.data } + }) + + makeDeployment({ app_uid: uid, organization_uid: organizationUid, data: { uid: deploymentUid, organization_uid: organizationUid } }) + .fetch() + .then((deployment) => { + checkDeployment(deployment) + done() + }) + .catch(done) + }) + + it('test get deployment logs from uid', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + const organizationUid = appMock.organization_uid + const deploymentUid = latestLiveResponse.data.uid + const content = { + message: 'No build command set', + stage: 'CREATING_BUILD', + timestamp: '2023-01-17T10:15:07.397Z' + } + mock.onGet(`manifests/${uid}/hosting/deployments/${deploymentUid}/logs`).reply(200, { + data: [content] + }) + + makeDeployment({ app_uid: uid, organization_uid: organizationUid, data: { uid: deploymentUid } }) + .logs() + .then((logs) => { + logs.forEach(log => { + expect(log.message).to.be.equal(content.message) + expect(log.stage).to.be.equal(content.stage) + expect(log.timestamp).to.be.equal(content.timestamp) + }) + done() + }) + .catch(done) + }) + + it('test get deployment logs from uid', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + const organizationUid = appMock.organization_uid + const deploymentUid = latestLiveResponse.data.uid + const content = { + download_url: 'download_url', + expires_in: 900 + } + mock.onPost(`manifests/${uid}/hosting/deployments/${deploymentUid}/signedDownloadUrl`).reply(200, { + data: { ...content } + }) + + makeDeployment({ app_uid: uid, organization_uid: organizationUid, data: { uid: deploymentUid } }) + .signedDownloadUrl() + .then((download) => { + expect(download.download_url).to.be.equal(content.download_url) + expect(download.expires_in).to.be.equal(content.expires_in) + done() + }) + .catch(done) + }) +}) + +function makeDeployment (data, param = {}) { + return new Deployment(Axios, data, param) +} + +function checkDeployment (deployment) { + expect(deployment.created_at).to.be.equal(latestLiveResponse.data.created_at) + expect(deployment.deployment_number).to.be.equal(latestLiveResponse.data.deployment_number) + expect(deployment.deployment_url).to.be.equal(latestLiveResponse.data.deployment_url) + expect(deployment.environment).to.be.equal(latestLiveResponse.data.environment) + expect(deployment.latest).to.be.equal(latestLiveResponse.data.latest) + expect(deployment.preview_url).to.be.equal(latestLiveResponse.data.preview_url) + expect(deployment.status).to.be.equal(latestLiveResponse.data.status) + expect(deployment.uid).to.be.equal(latestLiveResponse.data.uid) + expect(deployment.updated_at).to.be.equal(latestLiveResponse.data.updated_at) +} diff --git a/test/unit/entry-test.js b/test/unit/entry-test.js index d7bb3708..db87571d 100644 --- a/test/unit/entry-test.js +++ b/test/unit/entry-test.js @@ -291,7 +291,7 @@ describe('Contentstack Entry test', () => { }) it('Entry set Workflow stage test', done => { - var mock = new MockAdapter(Axios); + var mock = new MockAdapter(Axios) mock.onPost('/content_types/content_type_uid/entries/UID/workflow').reply(200, { ...noticeMock @@ -303,18 +303,50 @@ describe('Contentstack Entry test', () => { due_date: 'Thu Dec 01 2018', notify: true, assigned_to: [{ - uid: "user_uid", - name: "Username", - email: "user_email_id" - }], - assigned_by_roles: [{ - uid: "role_uid", - name: "Role name" - }] + uid: 'user_uid', + name: 'Username', + email: 'user_email_id' + }], + assigned_by_roles: [{ + uid: 'role_uid', + name: 'Role name' + }] } - makeEntry({entry: { ...systemUidMock }}) - .setWorkflowStage({workflow_stage, locale: 'en-us'}) + makeEntry({ entry: { ...systemUidMock } }) + .setWorkflowStage({ workflow_stage, locale: 'en-us' }) + .then((response) => { + expect(response.notice).to.be.equal(noticeMock.notice) + done() + }) + .catch(done) + }) + + it('Entry set Workflow stage test', done => { + var mock = new MockAdapter(Axios) + + mock.onPost('/content_types/content_type_uid/entries/UID/workflow').reply(200, { + ...noticeMock + }) + + const workflow_stage = { + uid: 'uid', + comment: 'Please review this.', + due_date: 'Thu Dec 01 2018', + notify: true, + assigned_to: [{ + uid: 'user_uid', + name: 'Username', + email: 'user_email_id' + }], + assigned_by_roles: [{ + uid: 'role_uid', + name: 'Role name' + }] + } + + makeEntry({ entry: { ...systemUidMock }, stackHeaders: stackHeadersMock }) + .setWorkflowStage({ workflow_stage, locale: 'en-us' }) .then((response) => { expect(response.notice).to.be.equal(noticeMock.notice) done() diff --git a/test/unit/hosting-test.js b/test/unit/hosting-test.js new file mode 100644 index 00000000..973e4ce3 --- /dev/null +++ b/test/unit/hosting-test.js @@ -0,0 +1,163 @@ +import Axios from 'axios' +import { expect } from 'chai' +import { describe, it } from 'mocha' +import MockAdapter from 'axios-mock-adapter' +import { appMock } from './mock/objects' +import { Hosting } from '../../lib/app/hosting' +import { latestLiveResponse, signedUrlResponse } from './mock/hosting-mock' + +describe('Contentstack hosting test', () => { + it('Hosting without contents', done => { + const hosting = makeHosting() + expect(hosting).to.not.equal(undefined) + expect(hosting.createUploadUrl).to.be.equal(undefined) + expect(hosting.deployment).to.be.equal(undefined) + expect(hosting.disable).to.be.equal(undefined) + expect(hosting.enable).to.be.equal(undefined) + expect(hosting.isEnable).to.be.equal(undefined) + expect(hosting.latestLiveDeployment).to.be.equal(undefined) + expect(hosting.params).to.not.equal(undefined) + expect(hosting.urlPath).to.be.equal(undefined) + done() + }) + it('Hosting without app uid', done => { + const hosting = makeHosting({}) + expect(hosting).to.not.equal(undefined) + expect(hosting.createUploadUrl).to.be.equal(undefined) + expect(hosting.deployment).to.be.equal(undefined) + expect(hosting.disable).to.be.equal(undefined) + expect(hosting.enable).to.be.equal(undefined) + expect(hosting.isEnable).to.be.equal(undefined) + expect(hosting.latestLiveDeployment).to.be.equal(undefined) + expect(hosting.params).to.not.equal(undefined) + expect(hosting.urlPath).to.be.equal(undefined) + done() + }) + + it('Hosting with app uid', done => { + const appUid = 'APP_UID' + const hosting = makeHosting({ app_uid: appUid }) + expect(hosting).to.not.equal(undefined) + expect(hosting.createUploadUrl).to.not.equal(undefined) + expect(hosting.deployment).to.not.equal(undefined) + expect(hosting.disable).to.not.equal(undefined) + expect(hosting.enable).to.not.equal(undefined) + expect(hosting.isEnable).to.not.equal(undefined) + expect(hosting.latestLiveDeployment).to.not.equal(undefined) + expect(hosting.params).to.not.equal(undefined) + expect(hosting.urlPath).to.be.equal(`/manifests/${appUid}/hosting`) + expect(hosting.deployment()).to.not.equal(undefined) + expect(hosting.deployment('uid')).to.not.equal(undefined) + done() + }) + + it('Hosting with app uid and org uid', done => { + const appUid = 'APP_UID' + const organizationUid = 'ORG_UID' + const hosting = makeHosting({ app_uid: appUid, organization_uid: organizationUid }) + expect(hosting).to.not.equal(undefined) + expect(hosting.createUploadUrl).to.not.equal(undefined) + expect(hosting.deployment).to.not.equal(undefined) + expect(hosting.disable).to.not.equal(undefined) + expect(hosting.enable).to.not.equal(undefined) + expect(hosting.isEnable).to.not.equal(undefined) + expect(hosting.latestLiveDeployment).to.not.equal(undefined) + expect(hosting.params.organization_uid).to.be.equal(organizationUid) + expect(hosting.urlPath).to.be.equal(`/manifests/${appUid}/hosting`) + done() + }) + + it('test hosting is enable request', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + mock.onGet(`manifests/${uid}/hosting`).reply(200, { + data: { enabled: false } + }) + + makeHosting({ app_uid: uid }) + .isEnable() + .then((response) => { + expect(response.data.enabled).to.be.equal(false) + done() + }) + .catch(done) + }) + + it('test set hosting enable', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + mock.onPatch(`manifests/${uid}/hosting/enable`).reply(200, { + data: { enabled: true } + }) + + makeHosting({ app_uid: uid }) + .enable() + .then((response) => { + expect(response.data.enabled).to.be.equal(true) + done() + }) + .catch(done) + }) + + it('test set hosting disble', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + mock.onPatch(`manifests/${uid}/hosting/disable`).reply(200, { + data: { enabled: false } + }) + + makeHosting({ app_uid: uid }) + .disable() + .then((response) => { + expect(response.data.enabled).to.be.equal(false) + done() + }) + .catch(done) + }) + + it('test create signed url for hosting', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + mock.onPost(`manifests/${uid}/hosting/signedUploadUrl`).reply(200, { + ...signedUrlResponse + }) + + makeHosting({ app_uid: uid }) + .createUploadUrl() + .then((response) => { + expect(response.data.upload_uid).to.be.equal(signedUrlResponse.data.upload_uid) + expect(response.data.upload_url).to.be.equal(signedUrlResponse.data.upload_url) + expect(response.data.expires_in).to.be.equal(signedUrlResponse.data.expires_in) + done() + }) + .catch(done) + }) + + it('test latest deployment for hosting', done => { + const mock = new MockAdapter(Axios) + const uid = appMock.uid + mock.onGet(`manifests/${uid}/hosting/latestLiveDeployment`).reply(200, { + ...latestLiveResponse + }) + + makeHosting({ app_uid: uid }) + .latestLiveDeployment() + .then((deployment) => { + expect(deployment.created_at).to.be.equal(latestLiveResponse.data.created_at) + expect(deployment.deployment_number).to.be.equal(latestLiveResponse.data.deployment_number) + expect(deployment.deployment_url).to.be.equal(latestLiveResponse.data.deployment_url) + expect(deployment.environment).to.be.equal(latestLiveResponse.data.environment) + expect(deployment.latest).to.be.equal(latestLiveResponse.data.latest) + expect(deployment.preview_url).to.be.equal(latestLiveResponse.data.preview_url) + expect(deployment.status).to.be.equal(latestLiveResponse.data.status) + expect(deployment.uid).to.be.equal(latestLiveResponse.data.uid) + expect(deployment.updated_at).to.be.equal(latestLiveResponse.data.updated_at) + done() + }) + .catch(done) + }) +}) + +function makeHosting (data, param = {}) { + return new Hosting(Axios, data, param) +} diff --git a/test/unit/index.js b/test/unit/index.js index c9677966..99b7fafe 100644 --- a/test/unit/index.js +++ b/test/unit/index.js @@ -28,3 +28,7 @@ require('./deliveryToken-test') require('./entry-test') require('./apps-test') require('./installation-test') +require('./hosting-test') +require('./deployment-test') +require('./app-request-test') +require('./authorization-test') diff --git a/test/unit/installation-test.js b/test/unit/installation-test.js index aa1307cc..811237dc 100644 --- a/test/unit/installation-test.js +++ b/test/unit/installation-test.js @@ -7,23 +7,33 @@ import { appInstallMock, appMock, installationMock } from './mock/objects' import { Installation } from '../../lib/app/installation' describe('Contentstack apps installation test', () => { - it('Installation without installation uid', done => { + it('Installation without installation uid', done => { const installation = makeInstallation({}) expect(installation.urlPath).to.be.equal(undefined) expect(installation.fetch).to.be.equal(undefined) expect(installation.update).to.be.equal(undefined) expect(installation.uninstall).to.be.equal(undefined) expect(installation.findAll).to.be.equal(undefined) + expect(installation.installationData).to.be.equal(undefined) + expect(installation.configuration).to.be.equal(undefined) + expect(installation.setConfiguration).to.be.equal(undefined) + expect(installation.serverConfig).to.be.equal(undefined) + expect(installation.setServerConfig).to.be.equal(undefined) done() }) it('Installation with app uid', done => { const uid = appMock.uid const installation = makeInstallation({ app_uid: uid }) - expect(installation.urlPath).to.be.equal(`apps/${uid}/installations`) + expect(installation.urlPath).to.be.equal(`manifests/${uid}/installations`) expect(installation.fetch).to.be.equal(undefined) expect(installation.update).to.be.equal(undefined) expect(installation.uninstall).to.be.equal(undefined) + expect(installation.installationData).to.be.equal(undefined) + expect(installation.configuration).to.be.equal(undefined) + expect(installation.setConfiguration).to.be.equal(undefined) + expect(installation.serverConfig).to.be.equal(undefined) + expect(installation.setServerConfig).to.be.equal(undefined) expect(installation.findAll).to.not.equal(undefined) done() }) @@ -35,6 +45,11 @@ describe('Contentstack apps installation test', () => { expect(installation.fetch).to.not.equal(undefined) expect(installation.update).to.not.equal(undefined) expect(installation.uninstall).to.not.equal(undefined) + expect(installation.installationData).to.not.equal(undefined) + expect(installation.configuration).to.not.equal(undefined) + expect(installation.setConfiguration).to.not.equal(undefined) + expect(installation.serverConfig).to.not.equal(undefined) + expect(installation.setServerConfig).to.not.equal(undefined) expect(installation.findAll).to.be.equal(undefined) done() }) @@ -62,7 +77,7 @@ describe('Contentstack apps installation test', () => { it('Install app test', done => { const mock = new MockAdapter(Axios) const uid = appMock.uid - mock.onPost(`/apps/${uid}/install`).reply(200, { + mock.onPost(`/manifests/${uid}/install`).reply(200, { data: { ...appInstallMock } @@ -83,7 +98,7 @@ describe('Contentstack apps installation test', () => { it('Get app installation test', done => { const mock = new MockAdapter(Axios) const uid = appMock.uid - mock.onGet(`apps/${uid}/installations`).reply(200, { + mock.onGet(`manifests/${uid}/installations`).reply(200, { data: [installationMock] }) @@ -114,6 +129,22 @@ describe('Contentstack apps installation test', () => { .catch(done) }) + it('Get installation installationData test', done => { + const mock = new MockAdapter(Axios) + const uid = installationMock.uid + mock.onGet(`/installations/${uid}/installationData`).reply(200, { + data: {} + }) + + makeInstallation({ data: { uid } }) + .installationData() + .then((data) => { + expect(data).to.not.equal(null) + done() + }) + .catch(done) + }) + it('Get installation configuration test', done => { const mock = new MockAdapter(Axios) const uid = installationMock.uid @@ -123,8 +154,53 @@ describe('Contentstack apps installation test', () => { makeInstallation({ data: { uid } }) .configuration() - .then((configuration) => { - expect(configuration).to.deep.equal({}) + .then((data) => { + expect(data).to.not.equal(null) + done() + }) + .catch(done) + }) + it('Set installation configuration test', done => { + const mock = new MockAdapter(Axios) + const uid = installationMock.uid + mock.onPut(`/installations/${uid}/configuration`).reply(200, { + data: {} + }) + + makeInstallation({ data: { uid } }) + .setConfiguration({}) + .then((data) => { + expect(data).to.not.equal(null) + done() + }) + .catch(done) + }) + it('Get installation server config test', done => { + const mock = new MockAdapter(Axios) + const uid = installationMock.uid + mock.onGet(`/installations/${uid}/server-configuration`).reply(200, { + data: {} + }) + + makeInstallation({ data: { uid } }) + .serverConfig() + .then((data) => { + expect(data).to.not.equal(null) + done() + }) + .catch(done) + }) + it('Get installation installationData test', done => { + const mock = new MockAdapter(Axios) + const uid = installationMock.uid + mock.onPut(`/installations/${uid}/server-configuration`).reply(200, { + data: {} + }) + + makeInstallation({ data: { uid } }) + .setServerConfig({}) + .then((data) => { + expect(data).to.not.equal(null) done() }) .catch(done) diff --git a/test/unit/mock/hosting-mock.js b/test/unit/mock/hosting-mock.js new file mode 100644 index 00000000..19d5ad10 --- /dev/null +++ b/test/unit/mock/hosting-mock.js @@ -0,0 +1,33 @@ +const signedUrlResponse = { + data: { + upload_uid: 'upload_uid', + form_fields: [ + { + key: 'key', + value: 'value' + } + ], + upload_url: 'upload_url', + expires_in: 900 + } +} +const latestLiveResponse = { + data: + { + created_at: '2023-01-17T10:07:13.215Z', + deployment_number: 1, + deployment_url: 'deployment_url', + environment: 'environment', + latest: true, + preview_url: 'preview_url', + status: 'DEPLOYED', + uid: 'uid', + updated_at: '2023-01-17T10:15:11.360Z' + } + +} + +export { + signedUrlResponse, + latestLiveResponse +} diff --git a/test/unit/mock/request-mock.js b/test/unit/mock/request-mock.js new file mode 100644 index 00000000..ce1b4d9e --- /dev/null +++ b/test/unit/mock/request-mock.js @@ -0,0 +1,36 @@ +const requestMock = { + organization_uid: 'org_uid', + manifest: { + uid: 'app_uid', + name: 'Awesome App', + description: '', + icon: '', + visibility: 'private', + target_type: 'stack', + organization_uid: 'org_uid', + framework_version: '1.0', + version: 1, + created_by: { + uid: 'user_uid', + first_name: 'John', + last_name: 'Doe' + }, + updated_by: { + uid: 'user_uid', + first_name: 'John', + last_name: 'Doe' + }, + created_at: '2022-02-11T08:43:59.837Z', + updated_at: '2022-02-11T08:43:59.837Z' + }, + requested_by: { + uid: 'user_request_uid', + first_name: 'sample', + last_name: 'user' + }, + target_uid: 'target_uid', + created_at: '2023-01-17T09:40:20.464Z', + updated_at: '2023-01-17T09:40:20.464Z', + uid: 'request_uid' +} +export { requestMock } diff --git a/test/unit/organization-test.js b/test/unit/organization-test.js index 1276b23b..67f30f04 100644 --- a/test/unit/organization-test.js +++ b/test/unit/organization-test.js @@ -261,6 +261,8 @@ function checknonAdminFunction (organization) { expect(organization.getInvitations).to.not.equal(undefined) expect(organization.resendInvitation).to.not.equal(undefined) expect(organization.roles).to.not.equal(undefined) + expect(organization.app()).to.not.equal(undefined) + expect(organization.appRequest()).to.not.equal(undefined) } function checkAdminFunction (organization) { diff --git a/types/app/authorization.d.ts b/types/app/authorization.d.ts new file mode 100644 index 00000000..299929f3 --- /dev/null +++ b/types/app/authorization.d.ts @@ -0,0 +1,7 @@ +import { AnyProperty } from '../utility/fields'; + +export interface Authorization { + findAll(param?: AnyProperty): Promise + revokeAll(): Promise + revoke(authorizationUid: string): Promise +} \ No newline at end of file diff --git a/types/app/hosting.d.ts b/types/app/hosting.d.ts new file mode 100644 index 00000000..0dd07f87 --- /dev/null +++ b/types/app/hosting.d.ts @@ -0,0 +1,39 @@ +import { AnyProperty, SystemFields } from '../utility/fields'; +import { ContentstackCollection } from '../contentstackCollection' +export interface Hosting { + isEnable(): Promise + enable(): Promise + disable(): Promise + createUploadUrl(): Promise + deployment(): Deployments + deployment(uid: string): Deployment + latestLiveDeployment(): Promise +} + +export interface UploadDetails { + upload_uid: string + form_fields: AnyProperty[] + upload_url: string + expires_in: number +} + +export interface Deployments { + create(data: { uploadUid: string , fileType: string, withAdvancedOptions?: boolean}): Promise + findAll(param?: AnyProperty): Promise> +} + +export interface Deployment extends SystemFields { + fetch(): Promise + logs(): Promise + signedDownloadUrl(): Promise +} + +export interface DownloadDetails { + download_url: string + expires_in: number +} +export interface DeploymentLog{ + message: string + stage: string + timestamp: string +} \ No newline at end of file diff --git a/types/app/index.d.ts b/types/app/index.d.ts index 8035fa91..c8a0760f 100644 --- a/types/app/index.d.ts +++ b/types/app/index.d.ts @@ -1,6 +1,9 @@ import { ContentstackCollection } from "../contentstackCollection"; import { AnyProperty, SystemFields } from "../utility/fields"; import { Creatable, SystemFunction } from "../utility/operations"; +import { Pagination } from '../utility/pagination'; +import { Authorization } from './authorization'; +import { Hosting } from './hosting'; import { Installation, Installations } from "./installation"; export interface App extends SystemFields, SystemFunction { @@ -9,10 +12,19 @@ export interface App extends SystemFields, SystemFunction { install(data: {targetUid: string, targetType: AppTarget}): Promise installation(): Installations installation(uid: string): Installation + hosting(): Hosting + authorize(param: { + responseType: string, + clientId: string, + redirectUri: string, + scope: string, + state: string }): Promise + authorization(): Authorization } export interface Apps extends Creatable { findAll(param?: AnyProperty): Promise> + findAllAuthorized(param?: Pagination & AnyProperty): Promise } export interface AppData extends AnyProperty { @@ -28,13 +40,16 @@ export interface AppData extends AnyProperty { export interface AppOAuth extends AnyProperty { redirect_uri?: string app_token_config?: TokenConfig - user_token_config?: TokenConfig + user_token_config?: UserTokenConfig } export interface TokenConfig extends AnyProperty { enabled: boolean scopes: string[] } +export interface UserTokenConfig extends TokenConfig { + allow_pkce: boolean +} export interface AppWebhookChannel extends AppWebhook { target_url: string diff --git a/types/app/installation.d.ts b/types/app/installation.d.ts index 5970bcd8..e3f5e1b9 100644 --- a/types/app/installation.d.ts +++ b/types/app/installation.d.ts @@ -1,11 +1,15 @@ import { ContentstackCollection } from "../contentstackCollection"; import { AnyProperty, SystemFields } from "../utility/fields"; -export interface Installation extends SystemFields{ +export interface Installation extends SystemFields { update(param?: AnyProperty): Promise fetch(param?: AnyProperty): Promise uninstall(param?: AnyProperty): Promise configuration(param?: AnyProperty): Promise + setConfiguration(config: AnyProperty): Promise + serverConfig(param?: AnyProperty): Promise + setServerConfig(config: AnyProperty): Promise + installationData(): Promise } export interface Installations { diff --git a/types/app/request.d.ts b/types/app/request.d.ts new file mode 100644 index 00000000..d7a89a5f --- /dev/null +++ b/types/app/request.d.ts @@ -0,0 +1,7 @@ +import { AnyProperty } from '../utility/fields'; + +export interface AppRequest { + create(params: {appUid: string, targetUid: string}): Promise + delete(requestUid: string): Promise + findAll(param?: AnyProperty): Promise +} \ No newline at end of file diff --git a/types/contentstackClient.d.ts b/types/contentstackClient.d.ts index 601dc2d2..67799d05 100644 --- a/types/contentstackClient.d.ts +++ b/types/contentstackClient.d.ts @@ -61,7 +61,7 @@ export interface ContentstackClient { getUser(params?: Pagination & AnyProperty): Promise - stack(): Queryable + stack(query?: {organization_uid?: string}): Queryable stack(config: StackConfig): Stack organization(): Organizations diff --git a/types/organization.d.ts b/types/organization.d.ts index 50fe4fcc..0612b25f 100644 --- a/types/organization.d.ts +++ b/types/organization.d.ts @@ -6,6 +6,7 @@ import { Pagination } from './utility/pagination' import { AnyProperty, SystemFields } from './utility/fields' import { ContentstackCollection, Response } from './contentstackCollection' import { App, Apps } from './app' +import { AppRequest } from './app/request' export interface Organizations { fetchAll(params?: AnyProperty): Promise> @@ -23,6 +24,7 @@ export interface Organization extends SystemFields { roles(param?: Pagination & AnyProperty): Promise> app(): Apps app(uid: string): App + appRequest(): AppRequest } export interface OrganizationInvite {