diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index f09161f5..2307d489 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -13,3 +13,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --all-projects --fail-on=all + json: true + continue-on-error: true + - uses: contentstack/sca-policy@main diff --git a/package-lock.json b/package-lock.json index c6de406d..da722f34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -540,15 +540,15 @@ "license": "MIT" }, "node_modules/@contentstack/core": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.3.10.tgz", - "integrity": "sha512-sQ44WtmmC1pITSIldupZGSv2lIZrCxDIonWWa9XcVEyonf4rNRe/jcqRcYh2ph00iAVS+S4KPVq2V6jpaKphNw==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.3.11.tgz", + "integrity": "sha512-CwB7/l9MUhy64FBnHBFj/Es9h0GQREJCUwdkfQpiEAbe9WtLTg3kMeE6ooo7ByZmqVF3BHXKUa9hssyT9VwAYg==", "license": "MIT", "dependencies": { "axios": "^1.13.5", "axios-mock-adapter": "^2.1.0", "lodash": "^4.17.23", - "qs": "6.14.1", + "qs": "6.15.0", "tslib": "^2.8.1" } }, @@ -566,9 +566,9 @@ } }, "node_modules/@contentstack/utils": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@contentstack/utils/-/utils-1.7.1.tgz", - "integrity": "sha512-b/0t1malpJeFCNd9+1uN3BuO8mRn2b5+aNtrYEZ6YlSNjYNRu9IjqSxZ5Clhs5267950UV1ayhgFE8z3qre2eQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@contentstack/utils/-/utils-1.8.0.tgz", + "integrity": "sha512-pqCFbn2dynSCW6LUD2AH74LIy32dxxe52OL+HpUxNVXV5doFyClkFjP9toqdAZ81VbCEaOc4WK+VS/RdtMpxDA==", "license": "MIT" }, "node_modules/@cspotcode/source-map-support": { @@ -2069,9 +2069,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.2.tgz", - "integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", + "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", "dev": true, "license": "MIT", "dependencies": { @@ -2089,9 +2089,9 @@ } }, "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", "dev": true, "license": "MIT", "peer": true @@ -2357,9 +2357,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", @@ -2692,9 +2692,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", "dev": true, "funding": [ { @@ -3087,9 +3087,9 @@ "license": "MIT" }, "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3236,9 +3236,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "dev": true, "license": "ISC" }, @@ -5875,9 +5875,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -6434,9 +6434,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" diff --git a/src/common/types.ts b/src/common/types.ts index b4dfea59..d6eefd07 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -21,25 +21,25 @@ export type queryParams = { /** * Interface for creating Contentstack plugins - * + * * @example * ```typescript * import { ContentstackPlugin } from '@contentstack/delivery-sdk'; - * + * * class MyPlugin implements ContentstackPlugin { * onRequest(config: any): any { * // Modify request configuration * console.log('Processing request:', config.url); * return { ...config, headers: { ...config.headers, 'X-Custom-Header': 'value' } }; * } - * + * * onResponse(request: any, response: any, data: any): any { * // Process response data * console.log('Processing response:', response.status); * return { ...response, data: { ...data, processed: true } }; * } * } - * + * * const stack = contentstack.stack({ * apiKey: 'your-api-key', * deliveryToken: 'your-delivery-token', @@ -344,6 +344,8 @@ export interface FindResponse { assets?: T[]; global_fields?: T[]; count?: number; + taxonomies?: T[]; + terms?: T[]; } export interface LivePreviewQuery { @@ -367,3 +369,31 @@ export type LivePreview = { management_token?: string; preview_token?: string; }; + +export interface BaseTaxonomy { + uid: string; + name: string; + description?: string; + terms_count?: number; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string; + type: string; + ACL: ACL; + publish_details?: PublishDetails; +} + +export interface BaseTerm { + taxonomy_uid: string; + uid: string; + name: string; + created_by: string; + created_at: string; + updated_by: string; + updated_at: string; + children_count?: number; + depth?: number; + ACL: ACL; + publish_details?: PublishDetails; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 3891e499..fe851ca4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export type { ImageTransform } from './assets'; export type { AssetQuery } from './query'; export type { TaxonomyQuery } from './query'; export type { ContentTypeQuery } from './query'; +export type { Taxonomy } from './taxonomy'; export { ErrorMessages, ErrorCode } from './common/error-messages'; export default contentstack; diff --git a/src/query/taxonomy-query.ts b/src/query/taxonomy-query.ts index dfa2ca9e..7b4c345d 100644 --- a/src/query/taxonomy-query.ts +++ b/src/query/taxonomy-query.ts @@ -1,10 +1,31 @@ import { Query } from "./query"; -import { AxiosInstance } from "@contentstack/core"; +import { AxiosInstance, getData } from "@contentstack/core"; +import { FindResponse } from "../common/types"; export class TaxonomyQuery extends Query { - constructor(client: AxiosInstance) { - super(client, {}, {}); // will need make changes to Query class so that CT uid is not mandatory - this._client = client; - this._urlPath = `/taxonomies/entries`; - } + constructor(client: AxiosInstance) { + super(client, {}, {}); // will need make changes to Query class so that CT uid is not mandatory + this._client = client; + this._urlPath = `/taxonomies/entries`; + } + /** + * @method find + * @memberof TaxonomyQuery + * @description Fetches a list of all published taxonomies available in the stack. + * @returns {Promise>} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const taxonomyQuery = stack.taxonomy(); + * const result = await taxonomyQuery.find(); + */ + override async find(): Promise> { + this._urlPath = "/taxonomies"; + const response = await getData(this._client, this._urlPath, { + params: this._queryParams, + }); + + return response as FindResponse; + } }; diff --git a/src/query/term-query.ts b/src/query/term-query.ts new file mode 100644 index 00000000..f78e3373 --- /dev/null +++ b/src/query/term-query.ts @@ -0,0 +1,40 @@ +import { AxiosInstance, getData } from '@contentstack/core'; +import { FindResponse } from '../common/types'; + +/** + * @class TermQuery + * @description Represents a query for fetching multiple published terms from a taxonomy. Requires taxonomy_publish feature flag to be enabled. + */ +export class TermQuery { + private _taxonomyUid: string; + private _client: AxiosInstance; + private _urlPath: string; + _queryParams: { [key: string]: string | number } = {}; + + /** + * @constructor + * @param {AxiosInstance} client - The HTTP client instance + * @param {string} taxonomyUid - The taxonomy UID + */ + constructor(client: AxiosInstance, taxonomyUid: string) { + this._client = client; + this._taxonomyUid = taxonomyUid; + this._urlPath = `/taxonomies/${this._taxonomyUid}/terms`; + } + + /** + * @method find + * @memberof TermQuery + * @description Fetches a list of all published terms within a specific taxonomy. + * @returns {Promise>} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().find(); + */ + async find(): Promise> { + const response = await getData(this._client, this._urlPath, { params: this._queryParams }); + return response as FindResponse; + } +} diff --git a/src/stack/stack.ts b/src/stack/stack.ts index 1b3c867a..6413ec90 100644 --- a/src/stack/stack.ts +++ b/src/stack/stack.ts @@ -8,6 +8,7 @@ import { synchronization } from '../sync'; import { TaxonomyQuery } from '../query'; import { GlobalFieldQuery } from '../query'; import { GlobalField } from '../global-field'; +import { Taxonomy } from '../taxonomy'; export class Stack { readonly config: StackConfig; @@ -78,7 +79,11 @@ export class Stack { * const taxonomy = stack.taxonomy() // For taxonomy query object */ - taxonomy(): TaxonomyQuery { + taxonomy(): TaxonomyQuery; + taxonomy(uid: string): Taxonomy; + taxonomy(uid?: string): Taxonomy | TaxonomyQuery { + if (uid) return new Taxonomy(this._client, uid); + return new TaxonomyQuery(this._client); } diff --git a/src/taxonomy/index.ts b/src/taxonomy/index.ts new file mode 100644 index 00000000..e754ab0a --- /dev/null +++ b/src/taxonomy/index.ts @@ -0,0 +1,68 @@ +import { AxiosInstance, getData } from '@contentstack/core'; +import { TermQuery } from '../query/term-query'; +import { Term } from './term'; + +/** + * @class Taxonomy + * @description Represents a published taxonomy with methods to fetch taxonomy data and manage terms. Requires taxonomy_publish feature flag to be enabled. + */ +export class Taxonomy { + private _client: AxiosInstance; + private _taxonomyUid: string; + private _urlPath: string; + + _queryParams: { [key: string]: string | number } = {}; + + /** + * @constructor + * @param {AxiosInstance} client - The HTTP client instance + * @param {string} taxonomyUid - The taxonomy UID + */ + constructor(client: AxiosInstance, taxonomyUid: string) { + this._client = client; + this._taxonomyUid = taxonomyUid; + this._urlPath = `/taxonomies/${this._taxonomyUid}`; + } + + /** + * @method term + * @memberof Taxonomy + * @description Gets a specific term or creates a term query + * @param {string} [uid] - Optional term UID. If provided, returns a Term instance. If not provided, returns a TermQuery instance. + * @returns {Term | TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * // Get a specific term + * const term = stack.taxonomy('taxonomy_uid').term('term_uid'); + * // Get all terms + * const termQuery = stack.taxonomy('taxonomy_uid').term(); + */ + term(uid: string): Term; + term(): TermQuery; + term(uid?: string): Term | TermQuery { + if (uid) return new Term(this._client, this._taxonomyUid, uid); + + return new TermQuery(this._client, this._taxonomyUid); + } + + /** + * @method fetch + * @memberof Taxonomy + * @description Fetches the taxonomy data by UID + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').fetch(); + */ + async fetch(): Promise { + const response = await getData(this._client, this._urlPath); + + if (response.taxonomy) return response.taxonomy as T; + + return response; + } +} diff --git a/src/taxonomy/term.ts b/src/taxonomy/term.ts new file mode 100644 index 00000000..bfbb7657 --- /dev/null +++ b/src/taxonomy/term.ts @@ -0,0 +1,93 @@ +import { AxiosInstance, getData } from "@contentstack/core"; + +/** + * @class Term + * @description Represents a published taxonomy term with methods to fetch term data, locales, ancestors, and descendants. Requires taxonomy_publish feature flag to be enabled. + */ +export class Term { + protected _client: AxiosInstance; + private _taxonomyUid: string; + private _termUid: string; + private _urlPath: string; + + /** + * @constructor + * @param {AxiosInstance} client - The HTTP client instance + * @param {string} taxonomyUid - The taxonomy UID + * @param {string} termUid - The term UID + */ + constructor(client: AxiosInstance, taxonomyUid: string, termUid: string) { + this._client = client; + this._taxonomyUid = taxonomyUid; + this._termUid = termUid; + this._urlPath = `/taxonomies/${this._taxonomyUid}/terms/${this._termUid}`; + } + + /** + * @method locales + * @memberof Term + * @description Fetches all published, localized versions of a single term. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').locales(); + */ + async locales(): Promise { + const response = await getData(this._client, `${this._urlPath}/locales`); + if (response.locales) return response.locales as T; + return response; + } + + /** + * @method ancestors + * @memberof Term + * @description Fetches all ancestors of a single published term, up to the root. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').ancestors(); + */ + async ancestors(): Promise { + const response = await getData(this._client, `${this._urlPath}/ancestors`); + if (response.ancestors) return response.ancestors as T; + return response; + } + + /** + * @method descendants + * @memberof Term + * @description Fetches all descendants of a single published term. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').descendants(); + */ + async descendants(): Promise { + const response = await getData(this._client, `${this._urlPath}/descendants`); + if (response.descendants) return response.descendants as T; + return response; + } + + /** + * @method fetch + * @memberof Term + * @description Fetches all descendants of a single published term. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').fetch(); + */ + async fetch(): Promise { + const response = await getData(this._client, this._urlPath); + if (response.term) return response.term as T; + return response; + } +} diff --git a/test/api/taxonomy.spec.ts b/test/api/taxonomy.spec.ts new file mode 100644 index 00000000..f6f64fc6 --- /dev/null +++ b/test/api/taxonomy.spec.ts @@ -0,0 +1,33 @@ +/* eslint-disable no-console */ +/* eslint-disable promise/always-return */ +import { stackInstance } from '../utils/stack-instance'; +import { TTaxonomies, TTaxonomy } from './types'; +import dotenv from 'dotenv'; +import { TaxonomyQuery } from '../../src/query/taxonomy-query'; +import { Taxonomy } from '../../src/taxonomy'; + +dotenv.config() + +const stack = stackInstance(); +describe('ContentType API test cases', () => { + it('should give taxonomies when taxonomies method is called', async () => { + const result = await makeTaxonomies().find(); + expect(result).toBeDefined(); + }); + + it('should give a single taxonomy when taxonomy method is called with taxonomyUid', async () => { + const result = await makeTaxonomy('taxonomy_testing').fetch(); + expect(result).toBeDefined(); + }); +}); + +function makeTaxonomies(): TaxonomyQuery { + const taxonomies = stack.taxonomy(); + + return taxonomies; +} + +function makeTaxonomy(taxonomyUid: string): Taxonomy { + const taxonomy = stack.taxonomy(taxonomyUid); + return taxonomy; +} \ No newline at end of file diff --git a/test/api/term-query.spec.ts b/test/api/term-query.spec.ts new file mode 100644 index 00000000..f541164e --- /dev/null +++ b/test/api/term-query.spec.ts @@ -0,0 +1,23 @@ +import { TermQuery } from "../../src/query/term-query"; +import { stackInstance } from "../utils/stack-instance"; +import { TTerm } from "./types"; + +const stack = stackInstance(); + +describe("Terms API test cases", () => { + it("should check for terms is defined", async () => { + const result = await makeTerms("taxonomy_testing").find(); + if (result.terms) { + expect(result.terms).toBeDefined(); + expect(result.terms[0].taxonomy_uid).toBeDefined(); + expect(result.terms[0].uid).toBeDefined(); + expect(result.terms[0].created_by).toBeDefined(); + expect(result.terms[0].updated_by).toBeDefined(); + } + }); +}); +function makeTerms(taxonomyUid = ""): TermQuery { + const terms = stack.taxonomy(taxonomyUid).term(); + + return terms; +} diff --git a/test/api/term.spec.ts b/test/api/term.spec.ts new file mode 100644 index 00000000..e90a4113 --- /dev/null +++ b/test/api/term.spec.ts @@ -0,0 +1,42 @@ +import { Term } from "../../src/taxonomy/term"; +import { stackInstance } from "../utils/stack-instance"; +import { TTerm, TTerms } from "./types"; + +const stack = stackInstance(); + +describe("Terms API test cases", () => { + it("should get a term by uid", async () => { + const result = await makeTerms("vehicles").fetch(); + expect(result).toBeDefined(); + expect(result.taxonomy_uid).toBeDefined(); + expect(result.uid).toBeDefined(); + expect(result.created_by).toBeDefined(); + expect(result.updated_by).toBeDefined(); + }); + + it("should get locales for a term", async () => { + const result = await makeTerms("vehicles").locales(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms[0].name).toBeDefined(); + }); + + it("should get ancestors for a term", async () => { + const result = await makeTerms("sleeper").ancestors(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms[0].name).toBeDefined(); + }); + + it("should get descendants for a term", async () => { + const result = await makeTerms("vrl").descendants(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms[0].name).toBeDefined(); + }); +}); + +function makeTerms(termUid = ""): Term { + const terms = stack.taxonomy("taxonomy_testing").term(termUid); + return terms; +} diff --git a/test/api/types.ts b/test/api/types.ts index 776e3b2c..f59f73cd 100644 --- a/test/api/types.ts +++ b/test/api/types.ts @@ -86,3 +86,38 @@ export interface TContentType { export interface TContentTypes { content_types: TContentType[]; } + +export interface TTaxonomies { + taxonomies: TTaxonomy[]; +} + +export interface TTaxonomy { + uid: string; + name: string; + description?: string; + terms_count?: number; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string; + type: string; + publish_details?: PublishDetails; +} + +export interface TTerms { + terms: TTerm[]; +} + +export interface TTerm { + taxonomy_uid: string; + uid: string; + ancestors: TTerm[]; + name: string; + created_by: string; + created_at: string; + updated_by: string; + updated_at: string; + children_count?: number; + depth?: number; + publish_details?: PublishDetails; +} \ No newline at end of file diff --git a/test/unit/taxonomy.spec.ts b/test/unit/taxonomy.spec.ts new file mode 100644 index 00000000..e0f172db --- /dev/null +++ b/test/unit/taxonomy.spec.ts @@ -0,0 +1,47 @@ +import { TaxonomyQuery } from '../../src/lib/taxonomy-query'; +import { Taxonomy } from '../../src/lib/taxonomy'; +import { AxiosInstance, httpClient } from '@contentstack/core'; +import MockAdapter from 'axios-mock-adapter'; +import { taxonomyFindResponseDataMock } from '../utils/mocks'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; +import { Term } from '../../src/lib/term'; +import { TermQuery } from '../../src/lib/term-query'; + +describe('ta class', () => { + let taxonomies: TaxonomyQuery; + let taxonomy: Taxonomy; + let client: AxiosInstance; + let mockClient: MockAdapter; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + taxonomies = new TaxonomyQuery(client); + taxonomy = new Taxonomy(client, 'taxonomy_testing'); + }); + + it('should give term instance when term method is called with termUid', () => { + const query = taxonomy.term('termUid'); + expect(query).toBeInstanceOf(Term); + }); + + it('should give term query instance when term method is called without termUid', () => { + const query = taxonomy.term() + expect(query).toBeInstanceOf(TermQuery); + }); + + it('should return all taxonomies in the response data when successful', async () => { + mockClient.onGet('/taxonomies').reply(200, taxonomyFindResponseDataMock); + const response = await taxonomies.find(); + expect(response).toEqual(taxonomyFindResponseDataMock); + }); + + it('should return single taxonomy in the response data when successful', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing').reply(200, taxonomyFindResponseDataMock.taxonomies[0]); + const response = await taxonomy.fetch(); + expect(response).toEqual(taxonomyFindResponseDataMock.taxonomies[0]); + }); +}); diff --git a/test/unit/term-query.spec.ts b/test/unit/term-query.spec.ts new file mode 100644 index 00000000..8b09fa11 --- /dev/null +++ b/test/unit/term-query.spec.ts @@ -0,0 +1,26 @@ +import { TermQuery } from '../../src/lib/term-query'; +import { AxiosInstance, httpClient } from '@contentstack/core'; +import MockAdapter from 'axios-mock-adapter'; +import { termQueryFindResponseDataMock } from '../utils/mocks'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; + +describe('TermQuery class', () => { + let termQuery: TermQuery; + let client: AxiosInstance; + let mockClient: MockAdapter; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + termQuery = new TermQuery(client, 'taxonomy_testing'); + }); + + it('should return response data when successful', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryFindResponseDataMock); + const response = await termQuery.find(); + expect(response).toEqual(termQueryFindResponseDataMock); + }); +}); diff --git a/test/unit/term.spec.ts b/test/unit/term.spec.ts new file mode 100644 index 00000000..3d41c234 --- /dev/null +++ b/test/unit/term.spec.ts @@ -0,0 +1,49 @@ +import { AxiosInstance, httpClient } from '@contentstack/core'; +import MockAdapter from 'axios-mock-adapter'; +import { termQueryFindResponseDataMock, termLocalesResponseDataMock, termAncestorsResponseDataMock, termDescendantsResponseDataMock } from '../utils/mocks'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; +import { Term } from '../../src/lib/term'; +import { Taxonomy } from '../../src/lib/taxonomy'; + +describe('Term class', () => { + let term: Term; + let client: AxiosInstance; + let mockClient: MockAdapter; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + term = new Term(client, 'taxonomy_testing', 'term1'); + }); + + it('should fetch the term by uid response when fetch method is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1').reply(200, termQueryFindResponseDataMock.terms[0]); + + const response = await term.fetch(); + expect(response).toEqual(termQueryFindResponseDataMock.terms[0]); + }); + + it('should fetch locales for a term when locales() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/locales').reply(200, termLocalesResponseDataMock.terms); + + const response = await term.locales(); + expect(response).toEqual(termLocalesResponseDataMock.terms); + }); + + it('should fetch ancestors for a term when ancestors() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/ancestors').reply(200, termAncestorsResponseDataMock); + + const response = await term.ancestors(); + expect(response).toEqual(termAncestorsResponseDataMock); + }); + + it('should fetch descendants for a term when descendants() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/descendants').reply(200, termDescendantsResponseDataMock); + + const response = await term.descendants(); + expect(response).toEqual(termDescendantsResponseDataMock); + }); +}); diff --git a/test/utils/mocks.ts b/test/utils/mocks.ts index a265d0cd..14957541 100644 --- a/test/utils/mocks.ts +++ b/test/utils/mocks.ts @@ -1676,6 +1676,185 @@ const gfieldQueryFindResponseDataMock = { ] } +const taxonomyFindResponseDataMock = { + "taxonomies": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy testing", + "description": "", + "terms_count": 1, + "created_at": "2025-10-10T06:42:48.644Z", + "updated_at": "2025-10-10T06:42:48.644Z", + "created_by": "created_by", + "updated_by": "updated_by", + "type": "TAXONOMY", + "ACL": {}, + "publish_details": { + "time": "2025-10-10T08:01:48.174Z", + "user": "user", + "environment": "env", + "locale": "en-us" + } + } + ] +} + +const termLocalesResponseDataMock = { + terms: [] +} + +const termAncestorsResponseDataMock = { + "terms": [ + { + "uid": "vehicles", + "name": "vehicles", + "publish_details": { + "time": "2025-10-28T06:54:12.505Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + }, + { + "uid": "buses", + "name": "buses", + "publish_details": { + "time": "2025-10-28T06:54:12.514Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + }, + { + "uid": "vrl", + "name": "vrl", + "publish_details": { + "time": "2025-10-28T06:54:12.570Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + } + ] +} + +const termDescendantsResponseDataMock = { + "terms": [ + { + "taxonomy_uid": "taxonomy_testing", + "uid": "sleeper", + "ancestors": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy_testing", + "type": "TAXONOMY" + }, + { + "uid": "vehicles", + "name": "vehicles", + "type": "" + }, + { + "uid": "buses", + "name": "buses", + "type": "" + }, + { + "uid": "vrl", + "name": "vrl", + "type": "" + } + ], + "name": "sleeper", + "parent_uid": "vrl", + "created_by": "created_by", + "created_at": "2025-10-28T07:58:46.870Z", + "updated_by": "updated_by", + "updated_at": "2025-10-28T07:58:46.870Z", + "children_count": 0, + "depth": 4, + "ACL": {}, + "publish_details": { + "time": "2025-10-28T07:59:12.557Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + }, + { + "taxonomy_uid": "taxonomy_testing", + "uid": "intercity", + "ancestors": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy_testing", + "type": "TAXONOMY" + }, + { + "uid": "vehicles", + "name": "vehicles", + "type": "" + }, + { + "uid": "buses", + "name": "buses", + "type": "" + }, + { + "uid": "vrl", + "name": "vrl", + "type": "" + } + ], + "name": "intercity", + "parent_uid": "vrl", + "created_by": "created_by", + "created_at": "2025-10-28T07:58:46.870Z", + "updated_by": "updated_by", + "updated_at": "2025-10-28T07:58:46.870Z", + "children_count": 0, + "depth": 4, + "ACL": {}, + "publish_details": { + "time": "2025-10-28T07:59:12.565Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + } + ] +} + +const termQueryFindResponseDataMock = { + "terms": [ + { + "taxonomy_uid": "taxonomy_testing", + "uid": "term1", + "ancestors": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy testing", + "type": "TAXONOMY" + } + ], + "name": "term1", + "created_by": "created_by", + "created_at": "2025-10-10T06:43:13.799Z", + "updated_by": "updated_by", + "updated_at": "2025-10-10T06:43:13.799Z", + "children_count": 0, + "depth": 1, + "ACL": {}, + "publish_details": { + "time": "2025-10-10T08:01:48.351Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + } +] +} + const syncResult: any = { ...axiosGetMock.data }; export { @@ -1688,5 +1867,10 @@ export { entryFindMock, entryFetchMock, gfieldFetchDataMock, - gfieldQueryFindResponseDataMock + gfieldQueryFindResponseDataMock, + taxonomyFindResponseDataMock, + termQueryFindResponseDataMock, + termLocalesResponseDataMock, + termAncestorsResponseDataMock, + termDescendantsResponseDataMock, };