diff --git a/lib/entity.js b/lib/entity.js index 5ca89fc2..b3ed2a4f 100644 --- a/lib/entity.js +++ b/lib/entity.js @@ -84,7 +84,7 @@ export const create = ({ http, params }) => { try { const response = await http.post(this.urlPath, data, headers) if (response.data) { - return new this.constructor(http, parseData(response, this.stackHeaders, this.content_type_uid)) + return new this.constructor(http, parseData(response, this.stackHeaders, this.content_type_uid, this.taxonomy_uid)) } else { throw error(response) } @@ -144,7 +144,7 @@ export const update = (http, type, params = {}) => { } }) if (response.data) { - return new this.constructor(http, parseData(response, this.stackHeaders, this.content_type_uid)) + return new this.constructor(http, parseData(response, this.stackHeaders, this.content_type_uid, this.taxonomy_uid)) } else { throw error(response) } @@ -204,7 +204,7 @@ export const fetch = (http, type, params = {}) => { response.data[type]['content_type'] = response.data['content_type'] response.data[type]['schema'] = response.data['schema'] } - return new this.constructor(http, parseData(response, this.stackHeaders, this.content_type_uid)) + return new this.constructor(http, parseData(response, this.stackHeaders, this.content_type_uid, this.taxonomy_uid)) } else { throw error(response) } @@ -235,7 +235,7 @@ export const fetchAll = (http, wrapperCollection, params = {}) => { } } -export function parseData (response, stackHeaders, contentTypeUID) { +export function parseData (response, stackHeaders, contentTypeUID, taxonomy_uid) { const data = response.data || {} if (stackHeaders) { data.stackHeaders = stackHeaders @@ -243,6 +243,9 @@ export function parseData (response, stackHeaders, contentTypeUID) { if (contentTypeUID) { data.content_type_uid = contentTypeUID } + if (taxonomy_uid) { + data.taxonomy_uid = taxonomy_uid + } return data } @@ -266,4 +269,36 @@ export async function get (http, url, params, data) { } catch (err) { throw error(err) } +} + +export const move = (http, type, force = false, params = {}) => { + return async function (param = {}) { + try { + let updateData = {} + const json = cloneDeep(this) + delete json.parent_uid + if (type) { + updateData[type] = json + } else { + updateData = json + } + const headers = { + headers: { ...cloneDeep(this.stackHeaders), ...cloneDeep(params) }, + params: { + ...cloneDeep(param) + } + } || {} + if (force === true) { + headers.params.force = true + } + const response = await http.put(`${this.urlPath}/move`, updateData, headers) + if (response.data) { + return new this.constructor(http, parseData(response, this.stackHeaders, this.content_type_uid, this.taxonomy_uid)) + } else { + throw error(response) + } + } catch (err) { + throw error(err) + } + } } \ No newline at end of file diff --git a/lib/stack/taxonomy/index.js b/lib/stack/taxonomy/index.js index a4989712..ffd13b79 100644 --- a/lib/stack/taxonomy/index.js +++ b/lib/stack/taxonomy/index.js @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import cloneDeep from 'lodash/cloneDeep' import { create, @@ -6,13 +7,17 @@ import { update, deleteEntity } from '../../entity' +import { Terms, TermsCollection } from './terms' -export function Taxonomy (http, data) { +export function Taxonomy (http, data = {}) { this.stackHeaders = data.stackHeaders this.urlPath = `/taxonomies` if (data.taxonomy) { Object.assign(this, cloneDeep(data.taxonomy)) + if (data.taxonomy.terms) { + this.terms = new TermsCollection(http, { terms: data.taxonomy.terms, stackHeaders: data.stackHeaders }, this.uid) + } this.urlPath = `/taxonomies/${this.uid}` /** @@ -24,7 +29,7 @@ export function Taxonomy (http, data) { * import * as contentstack from '@contentstack/management' * const client = contentstack.client() * - * client.stack({ api_key: 'api_key'}).taxonomy('taxonomy_uid').fetch() + * client.stack({ api_key: 'api_key'}).taxonomy('taxonomyUid').fetch() * .then((taxonomy) => { * taxonomy.name = 'taxonomy name' * return taxonomy.update() @@ -43,7 +48,7 @@ export function Taxonomy (http, data) { * import * as contentstack from '@contentstack/management' * const client = contentstack.client() * - * client.stack({ api_key: 'api_key'}).taxonomy('taxonomy_uid').delete() + * client.stack({ api_key: 'api_key'}).taxonomy('taxonomyUid').delete() * .then((response) => console.log(response.notice)) * */ @@ -58,11 +63,20 @@ export function Taxonomy (http, data) { * import * as contentstack from '@contentstack/management' * const client = contentstack.client() * - * client.stack({ api_key: 'api_key'}).taxonomy('taxonomy_uid').fetch() + * client.stack({ api_key: 'api_key'}).taxonomy('taxonomyUid').fetch() * .then((taxonomy) => console.log(taxonomy)) * */ this.fetch = fetch(http, 'taxonomy') + + this.terms = (uid = '') => { + const data = { stackHeaders: this.stackHeaders } + data.taxonomy_uid = this.uid + if (uid) { + data.term = { uid: uid } + } + return new Terms(http, data) + } } else { /** * @description The Create taxonomy call is used to create a taxonomy. diff --git a/lib/stack/taxonomy/terms/index.js b/lib/stack/taxonomy/terms/index.js new file mode 100644 index 00000000..8a98b5f7 --- /dev/null +++ b/lib/stack/taxonomy/terms/index.js @@ -0,0 +1,209 @@ +import cloneDeep from 'lodash/cloneDeep' +import { + create, + fetch, + update, + query, + deleteEntity, + move, + parseData +} from '../../../entity' + +export function Terms (http, data) { + this.stackHeaders = data.stackHeaders + this.taxonomy_uid = data.taxonomy_uid + this.urlPath = `/taxonomies/${this.taxonomy_uid}/terms` + + if (data && data.term) { + Object.assign(this, cloneDeep(data.term)) + this.urlPath = `/taxonomies/${this.taxonomy_uid}/terms/${this.uid}` + + /** + * @description The Update terms call is used to update an existing term. + * @memberof Terms + * @func update + * @returns {Promise} Promise for Terms instance + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.stack({ api_key: 'api_key'}).terms('terms_uid').fetch() + * .then((terms) => { + * terms.name = 'terms name' + * return terms.update() + * }) + * .then((terms) => console.log(terms)) + * + */ + this.update = update(http, 'term') + + /** + * @description The Delete terms call is used to delete an existing term. + * @memberof Terms + * @func delete + * @returns {Promise} Response Object. + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.stack({ api_key: 'api_key'}).terms('terms_uid').delete() + * .then((response) => console.log(response.notice)) + * + */ + this.delete = deleteEntity(http) + + /** + * @description The Fetch terms call is used to fetch an existing term. + * @memberof Terms + * @func fetch + * @returns {Promise} Promise for Terms instance + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.stack({ api_key: 'api_key'}).terms('terms_uid').fetch() + * .then((terms) => console.log(terms)) + * + */ + this.fetch = fetch(http, 'term') + + /** + * @description The ancestors call is used to get all the ancestor terms of an existing term. + * @memberof Terms + * @func ancestors + * @returns {Promise} Promise for Terms instance + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.stack({ api_key: 'api_key'}).terms('terms_uid').ancestors() + * .then((terms) => console.log(terms)) + * + */ + this.ancestors = async (params = {}) => { + try { + const headers = { + headers: { ...cloneDeep(this.stackHeaders), ...cloneDeep(params) } + } + const response = await http.get(`${this.urlPath}/ancestors`, headers) + return parseData(response, this.stackHeaders) + } catch (err) { + console.error(err) + throw err + } + } + + /** + * @description The move call is used to existing term. + * @memberof Terms + * @func descendants + * @returns {Promise} Promise for Terms instance + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.stack({ api_key: 'api_key'}).terms('terms_uid').descendants() + * .then((terms) => console.log(terms)) + * + */ + this.descendants = async (params = {}) => { + try { + const headers = { + headers: { ...cloneDeep(this.stackHeaders), ...cloneDeep(params) } + } + const response = await http.get(`${this.urlPath}/descendants`, headers) + return parseData(response, this.stackHeaders) + } catch (err) { + console.error(err) + throw err + } + } + + /** + * @description The move call is used to update the parent uid. + * @memberof Terms + * @func anscestors + * @returns {Promise} Promise for Terms instance + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * const term = { + * parent_uid: 'parent_uid', + * order: 2 + * } + * client.stack({ api_key: 'api_key'}).terms('terms_uid').move(term) + * .then((terms) => console.log(terms)) + * + */ + this.move = move(http, 'term') + } else { + /** + * @description The Create terms call is used to create a terms. + * @memberof Terms + * @func create + * @returns {Promise} Promise for Terms instance + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * const terms = { + * uid: 'terms_testing1', + * name: 'terms testing', + * description: 'Description for terms testing' + * } + * client.stack({ api_key: 'api_key'}).terms().create({terms}) + * .then(terms) => console.log(terms) + * + */ + this.create = create({ http }) + + /** + * @description The Query on Terms will allow to fetch details of all Terms. + * @memberof Terms + * @param {Object} params - URI parameters + * @prop {Object} params.query - Queries that you can use to fetch filtered results. + * @func query + * @returns {Array} Array of Terms. + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.stack().terms().query().find() + * .then((terms) => console.log(terms) + */ + this.query = query({ http: http, wrapperCollection: TermsCollection }) + } + /** + * @description The Search terms call is used to search a term. + * @memberof Terms + * @func search + * @returns {Promise} Promise for Terms instance + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * const term_string = '' + * client.stack({ api_key: 'api_key'}).terms().search(term_string) + * .then(terms) => console.log(terms) + * + */ + this.search = async (term = '', params = {}) => { + try { + const headers = { + headers: { ...cloneDeep(this.stackHeaders), ...cloneDeep(params) } + } + const response = await http.get(`taxonomies/${this.taxonomy_uid}/terms?term=${term}`, headers) + return parseData(response, this.stackHeaders) + } catch (err) { + console.error(err) + throw err + } + } +} +export function TermsCollection (http, data) { + const obj = cloneDeep(data.terms) || [] + const termsCollection = obj.map((term) => { + return new Terms(http, { term: term, taxonomy_uid: data.taxonomy_uid, stackHeaders: data.stackHeaders }) + }) + return termsCollection +} diff --git a/test/api/terms-test.js b/test/api/terms-test.js new file mode 100644 index 00000000..7d10a9bd --- /dev/null +++ b/test/api/terms-test.js @@ -0,0 +1,137 @@ +import { describe, it, beforeEach } from 'mocha' +import { expect } from 'chai' +import { jsonReader } from '../utility/fileOperations/readwrite' +import { contentstackClient } from '../utility/ContentstackClient.js' + +var client = {} +var stack = {} + +const taxonomy_uid = '' +const term_uid = '' +const term = { + term: { + uid: 'term_test', + name: 'Term test' + }, + parent_uid: null +} + +describe('Terms API Test', () => { + beforeEach(() => { + const user = jsonReader('loggedinuser.json') + stack = jsonReader('stack.json') + client = contentstackClient(user.authtoken) + }) + + it('Create term', async () => { + try { + const response = await makeTerms(taxonomy_uid).create(term) + expect(response.notice).to.be.equal('Term created successfully.') + expect(response.uid).to.be.equal(term.term.uid) + } catch (err) { + console.log(err) + } + }) + + it('Query and get all terms', async () => { + try { + const response = await makeTerms(taxonomy_uid).query().find() + expect(response.items).to.be.an('array') + expect(response.items[0].uid).not.to.be.equal(null) + expect(response.items[0].name).not.to.be.equal(null) + } catch (err) { + console.log(err) + } + }) + + it('Fetch term from UID', async () => { + try { + const response = await makeTerms(taxonomy_uid, term_uid).fetch() + expect(response.uid).to.be.equal(term_uid) + expect(response.name).not.to.be.equal(null) + expect(response.created_by).not.to.be.equal(null) + expect(response.updated_by).not.to.be.equal(null) + } catch (err) { + console.log(err) + } + }) + + it('Update term', async () => { + try { + const response = await makeTerms(taxonomy_uid, term_uid).fetch() + .then((term) => { + term.name = 'fashion' + return term.update() + }) + expect(response.notice).to.be.equal('Term updated successfully.') + expect(response.uid).to.be.equal(term_uid) + expect(response.name).to.be.equal('fashion') + expect(response.created_by).not.to.be.equal(null) + expect(response.updated_by).not.to.be.equal(null) + } catch (err) { + console.log(err) + } + }) + + it('Delete term from UID', async () => { + try { + const response = await makeTerms(term_uid).delete() + expect(response.notice).to.be.equal('') + } catch (err) { + console.log(err) + } + }) + + it('Ancestors of the term given', async () => { + try { + const response = await makeTerms(taxonomy_uid, term_uid).ancestors() + expect(response.terms[0].uid).not.to.be.equal(null) + expect(response.terms[0].name).not.to.be.equal(null) + expect(response.terms[0].created_by).not.to.be.equal(null) + expect(response.terms[0].updated_by).not.to.be.equal(null) + } catch (err) { + console.log(err) + } + }) + + it('Descendants of the term given', async () => { + try { + const response = await makeTerms(taxonomy_uid, term_uid).descendants() + expect(response.terms.uid).not.to.be.equal(null) + expect(response.terms.name).not.to.be.equal(null) + expect(response.terms.created_by).not.to.be.equal(null) + expect(response.terms.updated_by).not.to.be.equal(null) + } catch (err) { + console.log(err) + } + }) + it('search term', async () => { + term_string = '' + try { + const response = await makeTerms(taxonomy_uid).search(term_string) + expect(response.terms).to.be.an('array') + } catch (err) { + console.log(err) + } + }) + it('move term', async () => { + try { + const term = { + parent_uid: 'parent_uid', + order: 2 + } + await makeTerms(taxonomy_uid, term_uid).move({ term }) + .then((term) => { + term.parent_uid = 'parent_uid' + console.log(term.move()) + return term.move() + }) + } catch (err) { + console.log(err) + } + }) +}) + +function makeTerms (taxonomy_uid, term_uid = null) { + return client.stack({ api_key: stack.api_key }).taxonomy(taxonomy_uid).terms(term_uid) +} diff --git a/test/test.js b/test/test.js index 30f75299..d7e30522 100644 --- a/test/test.js +++ b/test/test.js @@ -26,3 +26,4 @@ require('./api/label-test') require('./api/contentType-delete-test') require('./api/delete-test') require('./api/taxonomy-test') +require('./api/terms-test') diff --git a/test/unit/index.js b/test/unit/index.js index 77316e2a..2877b750 100644 --- a/test/unit/index.js +++ b/test/unit/index.js @@ -34,3 +34,4 @@ require('./app-request-test') require('./authorization-test') require('./auditLog-test') require('./taxonomy-test') +require('./terms-test') diff --git a/test/unit/mock/objects.js b/test/unit/mock/objects.js index 0948d14f..1dcae6ea 100644 --- a/test/unit/mock/objects.js +++ b/test/unit/mock/objects.js @@ -733,6 +733,47 @@ const taxonomyMock = { referenced_terms_count: 3, referenced_entries_count: 6 } +const termsMock = { + taxonomy_uid: 'taxonomy_uid', + uid: 'UID', + name: 'name', + parent_uid: 'term_2', + depth: 2, + children_count: 2, + referenced_entries_count: 2, + ancestors: [{ + uid: 'term_1', + name: 'Term 1', + parent_uid: null, + depth: 1, + children_count: 3, + referenced_entries_count: 3 + }, + { + uid: 'term_2', + name: 'Term 2', + parent_uid: 'term_1', + depth: 2, + children_count: 2, + referenced_entries_count: 2 + }], + descendants: [{ + uid: 'term_4', + name: 'Term 4', + parent_uid: 'term_3', + depth: 3, + children_count: 1, + referenced_entries_count: 2 + }, + { + uid: 'term_5', + name: 'Term 5', + parent_uid: 'term_4', + depth: 4, + children_count: 0, + referenced_entries_count: 4 + }] +} function mockCollection (mockData, type) { const mock = { @@ -803,6 +844,7 @@ export { auditLogsMock, auditLogItemMock, taxonomyMock, + termsMock, mockCollection, entryMockCollection, checkSystemFields diff --git a/test/unit/terms-test.js b/test/unit/terms-test.js new file mode 100644 index 00000000..67e93420 --- /dev/null +++ b/test/unit/terms-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 { Terms } from '../../lib/stack/taxonomy/terms' +import { systemUidMock, stackHeadersMock, termsMock, noticeMock } from './mock/objects' + +describe('Contentstack Term test', () => { + it('term create test', done => { + var mock = new MockAdapter(Axios) + mock.onPost(`/taxonomies/taxonomy_uid/terms`).reply(200, { + term: { + ...termsMock + } + }) + makeTerms() + .create() + .then((term) => { + checkTerms(term) + done() + }) + .catch(done) + }) + it('Term fetch test', done => { + var mock = new MockAdapter(Axios) + mock.onGet(`/taxonomies/taxonomy_uid/terms/UID`).reply(200, { + term: { + ...termsMock + } + }) + makeTerms({ + term: { + ...systemUidMock + }, + stackHeaders: stackHeadersMock + }) + .fetch() + .then((term) => { + checkTerms(term) + done() + }) + .catch(done) + }) + it('Terms query test', done => { + var mock = new MockAdapter(Axios) + mock.onGet(`/taxonomies/taxonomy_uid/terms`).reply(200, { + terms: [ + termsMock + ] + }) + makeTerms() + .query() + .find() + .then((terms) => { + checkTerms(terms.items[0]) + done() + }) + .catch(done) + }) + it('Term update test', done => { + var mock = new MockAdapter(Axios) + mock.onPut(`/taxonomies/taxonomy_uid/terms/UID`).reply(200, { + term: { + ...termsMock + } + }) + makeTerms({ + term: { + ...systemUidMock + }, + stackHeaders: stackHeadersMock + }) + .update() + .then((term) => { + checkTerms(term) + done() + }) + .catch(done) + }) + it('term delete test', done => { + var mock = new MockAdapter(Axios) + mock.onDelete(`/taxonomies/taxonomy_uid/terms/UID`).reply(200, { + ...noticeMock + }) + makeTerms({ + term: { + ...systemUidMock + }, + stackHeaders: stackHeadersMock + }) + .delete() + .then((term) => { + expect(term.notice).to.be.equal(noticeMock.notice) + done() + }) + .catch(done) + }) + it('term ancestors test', done => { + var mock = new MockAdapter(Axios) + mock.onGet(`/taxonomies/taxonomy_uid/terms/UID/ancestors`).reply(200, { + term: { + ...termsMock + } + }) + makeTerms({ + term: { + ...systemUidMock + }, + stackHeaders: stackHeadersMock + }) + .ancestors() + .then((terms) => { + expect(terms.term.uid).to.be.equal('UID') + expect(terms.term.parent_uid).to.be.equal('term_2') + expect(terms.term.ancestors[0].uid).to.be.equal('term_1') + expect(terms.term.ancestors[1].uid).to.be.equal('term_2') + expect(terms.term.ancestors[1].parent_uid).to.be.equal('term_1') + done() + }) + .catch(done) + }) + it('term descendants test', done => { + var mock = new MockAdapter(Axios) + mock.onGet(`/taxonomies/taxonomy_uid/terms/UID/descendants`).reply(200, { + term: { + ...termsMock + } + }) + makeTerms({ + term: { + ...systemUidMock + }, + stackHeaders: stackHeadersMock + }) + .descendants() + .then((terms) => { + expect(terms.term.uid).to.be.equal('UID') + expect(terms.term.descendants[0].uid).to.be.equal('term_4') + expect(terms.term.descendants[1].uid).to.be.equal('term_5') + expect(terms.term.descendants[1].parent_uid).to.be.equal('term_4') + done() + }) + .catch(done) + }) + it('Term test without term uid', done => { + const term = makeTerms() + expect(term.stackHeaders).to.be.equal(undefined) + expect(term.update).to.be.equal(undefined) + expect(term.delete).to.be.equal(undefined) + expect(term.fetch).to.be.equal(undefined) + expect(term.create).not.to.be.equal(undefined) + expect(term.query).not.to.be.equal(undefined) + done() + }) + it('Term test with term uid', done => { + const term = makeTerms({ + term: { + ...systemUidMock + } + }) + expect(term.urlPath).to.be.equal(`/taxonomies/taxonomy_uid/terms/${systemUidMock.uid}`) + expect(term.stackHeaders).to.be.equal(undefined) + expect(term.update).not.to.be.equal(undefined) + expect(term.delete).not.to.be.equal(undefined) + expect(term.fetch).not.to.be.equal(undefined) + expect(term.create).to.be.equal(undefined) + expect(term.query).to.be.equal(undefined) + done() + }) + it('term search test', done => { + var mock = new MockAdapter(Axios) + mock.onGet(`/taxonomies/taxonomy_uid/terms?term=UID`).reply(200, { + term: { + ...termsMock + } + }) + makeTerms() + .search('UID') + .then((terms) => { + expect(terms.term.uid).to.be.equal('UID') + done() + }) + .catch(done) + }) + it('term move test', done => { + var mock = new MockAdapter(Axios) + mock.onPut(`/taxonomies/taxonomy_uid/terms/UID/move`).reply(200, { + term: { + ...termsMock + } + }) + makeTerms({ + term: { + ...systemUidMock + }, + stackHeaders: stackHeadersMock + }) + .move() + .then((terms) => { + checkTerms(terms) + expect(terms.uid).to.be.equal('UID') + done() + }) + .catch(done) + }) +}) + +function makeTerms (data = {}) { + return new Terms(Axios, { taxonomy_uid: 'taxonomy_uid', ...data }) +} + +function checkTerms (terms) { + expect(terms.name).to.be.equal('name') +}