From 8fa939907a2cf05ce054a8bd9ce43c2170af8143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maya=20Karabu=C5=82a-Stysiak?= <4799048+mayakarabula@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:05:02 +0200 Subject: [PATCH] feat: add Resource Type to contentful-management [DANTE-1832] (#2429) * feat: add Resource Type to contentful-management [DANTE-1832] * feat: add tests * feat: add getMany and orgId to ResourceType APIs * fix: unit test * fix: remove resource type id in test * feat: update comments, add more tests * fix: wait 1s after deletion * fix: remove type id from getMany * feat: clean up urls * fix: types * fix: remove empty headers --------- Co-authored-by: Marouen Ben Salem --- lib/adapters/REST/endpoints/index.ts | 2 + lib/adapters/REST/endpoints/resource-type.ts | 48 +++ lib/common-types.ts | 22 ++ lib/entities/index.ts | 2 + lib/entities/resource-provider.ts | 52 ++- lib/entities/resource-type.ts | 140 ++++++++ lib/export-types.ts | 5 + lib/plain/common-types.ts | 2 + lib/plain/entities/resource-type.ts | 83 +++++ lib/plain/plain-client.ts | 6 + .../resource-provider-integration.ts | 36 ++ test/integration/resource-type-integration.ts | 335 ++++++++++++++++++ test/unit/entities/resource-provider-test.ts | 48 ++- test/unit/entities/resource-type-test.ts | 35 ++ test/unit/mocks/entities.js | 26 +- test/unit/plain/resource-type-test.ts | 80 +++++ 16 files changed, 916 insertions(+), 6 deletions(-) create mode 100644 lib/adapters/REST/endpoints/resource-type.ts create mode 100644 lib/entities/resource-type.ts create mode 100644 lib/plain/entities/resource-type.ts create mode 100644 test/integration/resource-type-integration.ts create mode 100644 test/unit/entities/resource-type-test.ts create mode 100644 test/unit/plain/resource-type-test.ts diff --git a/lib/adapters/REST/endpoints/index.ts b/lib/adapters/REST/endpoints/index.ts index e254c9db8..64dda715f 100644 --- a/lib/adapters/REST/endpoints/index.ts +++ b/lib/adapters/REST/endpoints/index.ts @@ -36,6 +36,7 @@ import * as PreviewApiKey from './preview-api-key' import * as Release from './release' import * as ReleaseAction from './release-action' import * as ResourceProvider from './resource-provider' +import * as ResourceType from './resource-type' import * as Role from './role' import * as ScheduledAction from './scheduled-action' import * as Snapshot from './snapshot' @@ -96,6 +97,7 @@ export default { Release, ReleaseAction, ResourceProvider, + ResourceType, Role, ScheduledAction, Snapshot, diff --git a/lib/adapters/REST/endpoints/resource-type.ts b/lib/adapters/REST/endpoints/resource-type.ts new file mode 100644 index 000000000..c3e998677 --- /dev/null +++ b/lib/adapters/REST/endpoints/resource-type.ts @@ -0,0 +1,48 @@ +import type { RawAxiosRequestHeaders } from 'axios' +import type { AxiosInstance } from 'contentful-sdk-core' +import * as raw from './raw' +import copy from 'fast-copy' +import type { CollectionProp } from '../../../common-types' +import { type GetResourceTypeParams } from '../../../common-types' +import type { RestEndpoint } from '../types' +import type { ResourceTypeProps, UpsertResourceTypeProps } from '../../../entities/resource-type' + +const getBaseUrl = ( + params: GetResourceTypeParams | Omit +) => + `/organizations/${params.organizationId}/app_definitions/${params.appDefinitionId}/resource_provider/resource_types` + +const getEntityUrl = (params: GetResourceTypeParams) => + `${getBaseUrl(params)}/${params.resourceTypeId}` + +export const get: RestEndpoint<'ResourceType', 'get'> = ( + http: AxiosInstance, + params: GetResourceTypeParams +) => { + return raw.get(http, getEntityUrl(params)) +} + +export const upsert: RestEndpoint<'ResourceType', 'upsert'> = ( + http: AxiosInstance, + params: GetResourceTypeParams, + rawData: UpsertResourceTypeProps, + headers?: RawAxiosRequestHeaders +) => { + const data = copy(rawData) + + return raw.put(http, getEntityUrl(params), data, { headers }) +} + +export const del: RestEndpoint<'ResourceType', 'delete'> = ( + http: AxiosInstance, + params: GetResourceTypeParams +) => { + return raw.del(http, getEntityUrl(params)) +} + +export const getMany: RestEndpoint<'ResourceType', 'getMany'> = ( + http: AxiosInstance, + params: Omit +) => { + return raw.get>(http, getBaseUrl(params)) +} diff --git a/lib/common-types.ts b/lib/common-types.ts index 4e469d27f..b2172bfc1 100644 --- a/lib/common-types.ts +++ b/lib/common-types.ts @@ -165,6 +165,7 @@ import type { ResourceProviderProps, UpsertResourceProviderProps, } from './entities/resource-provider' +import type { ResourceTypeProps, UpsertResourceTypeProps } from './entities/resource-type' export interface DefaultElements { toPlainObject(): TPlainObject @@ -598,6 +599,11 @@ type MRInternal = { (opts: MROpts<'ResourceProvider', 'upsert', UA>): MRReturn<'ResourceProvider', 'upsert'> (opts: MROpts<'ResourceProvider', 'delete', UA>): MRReturn<'ResourceProvider', 'delete'> + (opts: MROpts<'ResourceType', 'get', UA>): MRReturn<'ResourceType', 'get'> + (opts: MROpts<'ResourceType', 'upsert', UA>): MRReturn<'ResourceType', 'upsert'> + (opts: MROpts<'ResourceType', 'delete', UA>): MRReturn<'ResourceType', 'delete'> + (opts: MROpts<'ResourceType', 'getMany', UA>): MRReturn<'ResourceType', 'getMany'> + (opts: MROpts<'Role', 'get', UA>): MRReturn<'Role', 'get'> (opts: MROpts<'Role', 'getMany', UA>): MRReturn<'Role', 'getMany'> (opts: MROpts<'Role', 'getManyForOrganization', UA>): MRReturn<'Role', 'getManyForOrganization'> @@ -780,6 +786,20 @@ export type MRActions = { } delete: { params: GetResourceProviderParams; return: any } } + ResourceType: { + get: { params: GetResourceTypeParams; return: ResourceTypeProps } + getMany: { + params: Omit + return: CollectionProp + } + upsert: { + params: GetResourceTypeParams + payload: UpsertResourceTypeProps + headers?: RawAxiosRequestHeaders + return: ResourceTypeProps + } + delete: { params: GetResourceTypeParams; return: any } + } Http: { get: { params: { url: string; config?: RawAxiosRequestConfig }; return: any } patch: { params: { url: string; config?: RawAxiosRequestConfig }; payload: any; return: any } @@ -2096,6 +2116,8 @@ export type GetUserUIConfigParams = GetUIConfigParams export type GetResourceProviderParams = GetOrganizationParams & { appDefinitionId: string } +export type GetResourceTypeParams = GetResourceProviderParams & { resourceTypeId: string } + export type QueryParams = { query?: QueryOptions } export type SpaceQueryParams = { query?: SpaceQueryOptions } export type PaginationQueryParams = { query?: PaginationQueryOptions } diff --git a/lib/entities/index.ts b/lib/entities/index.ts index 5939512a8..5c49e61de 100644 --- a/lib/entities/index.ts +++ b/lib/entities/index.ts @@ -53,6 +53,7 @@ import * as workflowDefinition from './workflow-definition' import * as concept from './concept' import * as conceptScheme from './concept-scheme' import * as resourceProvider from './resource-provider' +import * as resourceType from './resource-type' export default { accessToken, @@ -92,6 +93,7 @@ export default { release, releaseAction, resourceProvider, + resourceType, role, scheduledAction, snapshot, diff --git a/lib/entities/resource-provider.ts b/lib/entities/resource-provider.ts index c06dade5c..8c6894c2d 100644 --- a/lib/entities/resource-provider.ts +++ b/lib/entities/resource-provider.ts @@ -1,7 +1,15 @@ -import type { BasicMetaSysProps, DefaultElements, MakeRequest, SysLink } from '../common-types' +import type { + BasicMetaSysProps, + CollectionProp, + DefaultElements, + MakeRequest, + SysLink, +} from '../common-types' import { toPlainObject, freezeSys } from 'contentful-sdk-core' import copy from 'fast-copy' import enhanceWithMethods from '../enhance-with-methods' +import type { ResourceType, UpsertResourceTypeProps } from './resource-type' +import entities from '.' export type ResourceProviderProps = { /** @@ -30,12 +38,17 @@ export interface ResourceProvider DefaultElements { upsert(): Promise delete(): Promise + upsertResourceType(id: string, data: UpsertResourceTypeProps): Promise + getResourceType(id: string): Promise + getResourceTypes(): Promise> } /** * @private */ function createResourceProviderApi(makeRequest: MakeRequest) { + const { wrapResourceType } = entities.resourceType + return { /** * Sends an update to the server with any changes made to the object's properties @@ -92,6 +105,43 @@ function createResourceProviderApi(makeRequest: MakeRequest) { params: getParams(data), }) }, + + getResourceType: function getResourceType(id: string) { + return makeRequest({ + entityType: 'ResourceType', + action: 'get', + params: { + organizationId: this.sys.organization.sys.id, + appDefinitionId: this.sys.appDefinition.sys.id, + resourceTypeId: id, + }, + }).then((data) => wrapResourceType(makeRequest, data)) + }, + upsertResourceType: function upsertResourceType(id: string, data: UpsertResourceTypeProps) { + return makeRequest({ + entityType: 'ResourceType', + action: 'upsert', + params: { + organizationId: this.sys.organization.sys.id, + appDefinitionId: this.sys.appDefinition.sys.id, + resourceTypeId: id, + }, + payload: data, + }).then((data) => wrapResourceType(makeRequest, data)) + }, + getResourceTypes: function getResourceTypes() { + return makeRequest({ + entityType: 'ResourceType', + action: 'getMany', + params: { + organizationId: this.sys.organization.sys.id, + appDefinitionId: this.sys.appDefinition.sys.id, + }, + }).then((data) => { + data.items = data.items.map((item) => wrapResourceType(makeRequest, item)) + return data as CollectionProp + }) + }, } } /** diff --git a/lib/entities/resource-type.ts b/lib/entities/resource-type.ts new file mode 100644 index 000000000..cb1ed9928 --- /dev/null +++ b/lib/entities/resource-type.ts @@ -0,0 +1,140 @@ +import type { + BasicMetaSysProps, + DefaultElements, + GetResourceTypeParams, + MakeRequest, + SysLink, +} from '../common-types' +import { toPlainObject, freezeSys } from 'contentful-sdk-core' +import copy from 'fast-copy' +import enhanceWithMethods from '../enhance-with-methods' + +export type ResourceTypeProps = { + /** + * System metadata + */ + sys: Omit & { + appDefinition: SysLink + organization: SysLink + resourceProvider: SysLink + } + /** + * Resource Type name + */ + name: string + /** + * Resource Type defaultFieldMapping + */ + defaultFieldMapping: { + title: string + subtitle?: string + description?: string + externalUrl?: string + image?: { + url: string + altText?: string + } + badge?: { + label: string + variant: string + } + } +} + +export type UpsertResourceTypeProps = Omit + +export interface ResourceType extends ResourceTypeProps, DefaultElements { + upsert(): Promise + delete(): Promise +} + +/** + * @private + */ +function createResourceTypeApi(makeRequest: MakeRequest) { + return { + /** + * Sends an update to the server with any changes made to the object's properties + * @return Object returned from the server with updated changes. + * @example ```javascript + * const contentful = require('contentful-management') + * + * const client = contentful.createClient({ + * accessToken: '' + * }) + * + * client.getOrganization('') + * .then((org) => org.getAppDefinition('')) + * .then((appDefinition) => appDefinition.getResourceType()) + * .then((resourceType) => { + * resourceType.name = '' + * return resourceType.upsert() + * }) + * .catch(console.error) + * ``` + */ + upsert: function upsert() { + const data = this.toPlainObject() as ResourceTypeProps + + return makeRequest({ + entityType: 'ResourceType', + action: 'upsert', + params: getParams(data), + headers: {}, + payload: getUpsertParams(data), + }).then((data) => wrapResourceType(makeRequest, data)) + }, + /** + * Deletes this object on the server. + * @return Promise for the deletion. It contains no data, but the Promise error case should be handled. + * @example ```javascript + * const contentful = require('contentful-management') + * + * const client = contentful.createClient({ + * accessToken: '' + * }) + * + * client.getOrganization('') + * .then((org) => org.getAppDefinition('')) + * .then((appDefinition) => appDefinition.getResourceType()) + * .then((resourceType) => resourceType.delete()) + * .catch(console.error) + * ``` + */ + delete: function del() { + const data = this.toPlainObject() as ResourceTypeProps + + return makeRequest({ + entityType: 'ResourceType', + action: 'delete', + params: getParams(data), + }) + }, + } +} + +const getParams = (data: ResourceTypeProps): GetResourceTypeParams => ({ + organizationId: data.sys.organization.sys.id, + appDefinitionId: data.sys.appDefinition.sys.id, + resourceTypeId: data.sys.id, +}) + +const getUpsertParams = (data: ResourceTypeProps): UpsertResourceTypeProps => ({ + name: data.name, + defaultFieldMapping: data.defaultFieldMapping, +}) + +/** + * @private + * @param makeRequest - function to make requests via an adapter + * @param data - Raw Resource Type data + * @return Wrapped Resource Type data + */ +export function wrapResourceType(makeRequest: MakeRequest, data: ResourceTypeProps): ResourceType { + const resourceType = toPlainObject(copy(data)) + const ResourceTypeWithMethods = enhanceWithMethods( + resourceType, + createResourceTypeApi(makeRequest) + ) + return freezeSys(ResourceTypeWithMethods) +} diff --git a/lib/export-types.ts b/lib/export-types.ts index 336319701..4ed69f003 100644 --- a/lib/export-types.ts +++ b/lib/export-types.ts @@ -287,3 +287,8 @@ export type { ResourceProviderProps, UpsertResourceProviderProps, } from './entities/resource-provider' +export type { + ResourceType, + ResourceTypeProps, + UpsertResourceTypeProps, +} from './entities/resource-type' diff --git a/lib/plain/common-types.ts b/lib/plain/common-types.ts index 0fced24b3..7214151da 100644 --- a/lib/plain/common-types.ts +++ b/lib/plain/common-types.ts @@ -114,6 +114,7 @@ import type { AppAccessTokenPlainClientAPI } from './entities/app-access-token' import type { ConceptPlainClientAPI } from './entities/concept' import type { ConceptSchemePlainClientAPI } from './entities/concept-scheme' import type { ResourceProviderPlainClientAPI } from './entities/resource-provider' +import type { ResourceTypePlainClientAPI } from './entities/resource-type' export type PlainClientAPI = { raw: { @@ -505,6 +506,7 @@ export type PlainClientAPI = { appDefinition: AppDefinitionPlainClientAPI appInstallation: AppInstallationPlainClientAPI resourceProvider: ResourceProviderPlainClientAPI + resourceType: ResourceTypePlainClientAPI extension: ExtensionPlainClientAPI webhook: WebhookPlainClientAPI snapshot: { diff --git a/lib/plain/entities/resource-type.ts b/lib/plain/entities/resource-type.ts new file mode 100644 index 000000000..ca70df782 --- /dev/null +++ b/lib/plain/entities/resource-type.ts @@ -0,0 +1,83 @@ +import type { RawAxiosRequestHeaders } from 'axios' + +import type { OptionalDefaults } from '../wrappers/wrap' +import type { CollectionProp, GetResourceTypeParams } from '../../common-types' +import type { ResourceTypeProps, UpsertResourceTypeProps } from '../../export-types' + +export type ResourceTypePlainClientAPI = { + /* + * Fetch a Resource Type + * @param params entity IDs to identify the Resource Type + * @returns the Resource Type + * @throws if the request fails, or the Resource Type is not found + * @example + * ```javascript + * const resourceType = await client.resourceType.get({ + * organizationId: '', + * appDefinitionId: '', + * resourceTypeId: '', + * }); + * ``` + */ + get(params: OptionalDefaults): Promise + + /* + * Creates or updates a Resource Type + * @param params entity IDs to identify the Resource Type + * @param rawData the ResourceType + * @returns the created or updated Resource Type + * @throws if the request fails, the App Definition or Resource Provider is not found, or the payload is malformed + * @example + * ```javascript + * // You need a valid AppDefinition with an activated AppBundle that has a contentful function configured + * const resourceType = await client.resourceType.upsert( + * { + * organizationId: '', + * appDefinitionId: '', + * resourceTypeId: '', + * }, + * rawData: { + * name: '', + * defaultFieldMapping: { + * title: '', + * }, + * } + * ); + * ``` + */ + upsert( + params: OptionalDefaults, + rawData: UpsertResourceTypeProps, + headers?: RawAxiosRequestHeaders + ): Promise + + /* + * Delete a ResourceType + * @param params entity IDs to identify the Resource Type + * @throws if the request fails, or the Resource Type is not found + * @example + * ```javascript + * await client.resourceType.delete({ + * organizationId: '', + * appDefinitionId: '', + * resourceTypeId: '', + * }); + * ``` + */ + delete(params: OptionalDefaults): Promise + + /* + * Fetch all Resource Types + * @returns all Resource Types + * @example + * ```javascript + * const resourceTypes = await client.resourceType.getMany({ + * organizationId: '', + * appDefinitionId: '', + * }); + * ``` + */ + getMany( + params: OptionalDefaults> + ): Promise> +} diff --git a/lib/plain/plain-client.ts b/lib/plain/plain-client.ts index fba79a821..7acb31141 100644 --- a/lib/plain/plain-client.ts +++ b/lib/plain/plain-client.ts @@ -378,6 +378,12 @@ export const createPlainClient = ( upsert: wrap(wrapParams, 'ResourceProvider', 'upsert'), delete: wrap(wrapParams, 'ResourceProvider', 'delete'), }, + resourceType: { + get: wrap(wrapParams, 'ResourceType', 'get'), + getMany: wrap(wrapParams, 'ResourceType', 'getMany'), + upsert: wrap(wrapParams, 'ResourceType', 'upsert'), + delete: wrap(wrapParams, 'ResourceType', 'delete'), + }, extension: { get: wrap(wrapParams, 'Extension', 'get'), getMany: wrap(wrapParams, 'Extension', 'getMany'), diff --git a/test/integration/resource-provider-integration.ts b/test/integration/resource-provider-integration.ts index 595c48f38..9e6eb41d7 100644 --- a/test/integration/resource-provider-integration.ts +++ b/test/integration/resource-provider-integration.ts @@ -122,6 +122,42 @@ describe('ResourceProvider API', () => { ) }) + test('upsertResourceType', async () => { + const resourceProvider = await appDefinition.upsertResourceProvider({ + sys: { id: 'test' }, + type: 'function', + function: { sys: { id: functionManifest.id, type: 'Link', linkType: 'Function' } }, + }) + + const resourceType = await resourceProvider.upsertResourceType('test:resourceTypeId', { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + }) + + expect(resourceType.sys.id).to.equal('test:resourceTypeId') + }) + + test('getResourceType', async () => { + const resourceProvider = await appDefinition.upsertResourceProvider({ + sys: { id: 'test' }, + type: 'function', + function: { sys: { id: functionManifest.id, type: 'Link', linkType: 'Function' } }, + }) + + await resourceProvider.upsertResourceType('test:resourceTypeId', { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + }) + + const resourceType = await resourceProvider.getResourceType('test:resourceTypeId') + + expect(resourceType.name).to.equal('resourceType') + }) + describe('PlainClient', async () => { const plainClient = initPlainClient() test('create ResourceProvider', async () => { diff --git a/test/integration/resource-type-integration.ts b/test/integration/resource-type-integration.ts new file mode 100644 index 000000000..a6ce3b7b8 --- /dev/null +++ b/test/integration/resource-type-integration.ts @@ -0,0 +1,335 @@ +import { test } from 'mocha' +import { readFileSync } from 'fs' +import { expect } from 'chai' +import { getTestOrganization, initPlainClient } from '../helpers' +import type { Organization } from '../../lib/entities/organization' +import type { AppDefinition } from '../../lib/entities/app-definition' +import type { AppUpload } from '../../lib/entities/app-upload' +import type { AppBundle } from '../../lib/entities/app-bundle' +import type { ResourceProvider, ResourceType, ResourceTypeProps } from '../../lib/export-types' + +describe('ResourceType API', () => { + const functionManifest = { + id: 'tmdbMockFunction', + name: 'Mocked TMDB lookup function', + description: 'This is a mocked example to help test Apps with ERL.', + path: 'functions/index.js', + allowNetworks: ['api.themoviedb.org'], + accepts: ['resources.lookup', 'resources.search'], + } + + let organization: Organization + let appDefinition: AppDefinition + let appDefinitionNoProvider: AppDefinition + let appUpload: AppUpload + let appBundle: AppBundle + let resourceProvider: ResourceProvider + let resourceType: ResourceType | null + let resourceTypePlain: ResourceTypeProps | null + + before(async () => { + organization = (await getTestOrganization()) as Organization + appDefinition = await organization.createAppDefinition({ + name: 'Test', + src: 'http://localhost:2222', + locations: [{ location: 'entry-sidebar' }], + }) + + appUpload = await organization.createAppUpload(readFileSync(`${__dirname}/fixtures/build.zip`)) + appBundle = await appDefinition.createAppBundle({ + appUploadId: appUpload.sys.id, + comment: 'Testing ResourceTypeCreation', + functions: [functionManifest], + }) + + appDefinition.bundle = { sys: { id: appBundle.sys.id, type: 'Link', linkType: 'AppBundle' } } + appDefinition.src = undefined + + appDefinition = await appDefinition.update() + + resourceProvider = await appDefinition.upsertResourceProvider({ + sys: { id: 'resourceProvider' }, + type: 'function', + function: { sys: { id: functionManifest.id, type: 'Link', linkType: 'Function' } }, + }) + + appDefinitionNoProvider = await organization.createAppDefinition({ + name: 'TestNoProvider', + src: 'http://localhost:2222', + locations: [{ location: 'entry-sidebar' }], + }) + }) + + beforeEach(() => { + resourceType = null + resourceTypePlain = null + }) + + afterEach(async () => { + if (resourceType) { + resourceType.delete() + } + + if (resourceTypePlain) { + ;(await resourceProvider.getResourceType('resourceProvider:resourceTypeId')).delete() + } + + await new Promise((resolve) => setTimeout(resolve, 1000)) + }) + + after(async () => { + if (resourceProvider) { + await resourceProvider.delete() + } + + if (appUpload) { + await appUpload.delete() + } + + if (appDefinition) { + await appDefinition.delete() + } + + if (appDefinitionNoProvider) { + await appDefinitionNoProvider.delete() + } + }) + + test('create ResourceType', async () => { + resourceType = await resourceProvider.upsertResourceType('resourceProvider:resourceTypeId', { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + }) + + expect(resourceType.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(resourceType.name).to.equal('resourceType') + }) + + test('update ResourceType', async () => { + resourceType = await resourceProvider.upsertResourceType('resourceProvider:resourceTypeId', { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + }) + + resourceType.name = 'updatedResourceType' + + const updatedResourceType = await resourceType.upsert() + + expect(updatedResourceType.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(updatedResourceType.name).to.equal('updatedResourceType') + }) + + test('get ResourceType', async () => { + await resourceProvider.upsertResourceType('resourceProvider:resourceTypeId', { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + }) + + resourceType = await resourceProvider.getResourceType('resourceProvider:resourceTypeId') + + expect(resourceType.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(resourceType.name).to.equal('resourceType') + }) + + test('get ResourceTypes', async () => { + await resourceProvider.upsertResourceType('resourceProvider:resourceTypeId', { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + }) + + const response = await resourceProvider.getResourceTypes() + resourceType = response.items[0] + + expect(resourceType.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(resourceType.name).to.equal('resourceType') + }) + + test('delete ResourceType', async () => { + const resourceType = await resourceProvider.upsertResourceType( + 'resourceProvider:resourceTypeId', + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + await resourceType.delete() + + await expect( + resourceProvider.getResourceType('resourceProvider:resourceTypeId') + ).to.be.rejectedWith('The resource could not be found') + }) + + describe('PlainClient', async () => { + const plainClient = initPlainClient() + test('create ResourceType', async () => { + resourceTypePlain = await plainClient.resourceType.upsert( + { + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }, + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + expect(resourceTypePlain.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(resourceTypePlain.name).to.equal('resourceType') + }) + + test('creating ResourceType without Provider fails', async () => { + await expect( + plainClient.resourceType.upsert( + { + organizationId: organization.sys.id, + appDefinitionId: appDefinitionNoProvider.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }, + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + ).to.be.rejectedWith('The resource could not be found') + }) + + test('update ResourceType', async () => { + resourceTypePlain = await plainClient.resourceType.upsert( + { + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }, + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + const updatedResourceType = await plainClient.resourceType.upsert( + { + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }, + { + name: 'updatedResourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + expect(updatedResourceType.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(updatedResourceType.name).to.equal('updatedResourceType') + }) + + test('get ResourceType', async () => { + await plainClient.resourceType.upsert( + { + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }, + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + resourceTypePlain = await plainClient.resourceType.get({ + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }) + + expect(resourceTypePlain.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(resourceTypePlain.name).to.equal('resourceType') + }) + + test('get many ResourceTypes', async () => { + await plainClient.resourceType.upsert( + { + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }, + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + const response = await plainClient.resourceType.getMany({ + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + }) + + resourceTypePlain = response.items[0] + + expect(resourceTypePlain.sys.id).to.equal('resourceProvider:resourceTypeId') + expect(resourceTypePlain.name).to.equal('resourceType') + }) + + test('getMany returns empty array if no Resource Types are present', async () => { + const response = await plainClient.resourceType.getMany({ + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + }) + + expect(response.items).to.be.an('array').that.is.empty + }) + + test('delete ResourceType', async () => { + await plainClient.resourceType.upsert( + { + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }, + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + await plainClient.resourceType.delete({ + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }) + + await expect( + plainClient.resourceType.get({ + organizationId: organization.sys.id, + appDefinitionId: appDefinition.sys.id, + resourceTypeId: 'resourceProvider:resourceTypeId', + }) + ).to.be.rejectedWith('The resource could not be found') + }) + }) +}) diff --git a/test/unit/entities/resource-provider-test.ts b/test/unit/entities/resource-provider-test.ts index b6ad044f9..b3439a3cf 100644 --- a/test/unit/entities/resource-provider-test.ts +++ b/test/unit/entities/resource-provider-test.ts @@ -1,5 +1,5 @@ import type { ResourceProviderProps } from '../../../lib/entities/resource-provider' -import { cloneMock } from '../mocks/entities' +import { cloneMock, resourceTypeMock } from '../mocks/entities' import setupMakeRequest from '../mocks/makeRequest' import { wrapResourceProvider } from '../../../lib/entities/resource-provider' import { @@ -8,6 +8,9 @@ import { entityDeleteTest, } from '../test-creators/instance-entity-methods' import { describe, test } from 'mocha' +import { expect } from 'chai' +import type { ResourceTypeProps } from '../../../lib/entities/resource-type' +import type { CollectionProp } from '../../../lib/common-types' function setup(promise: Promise) { return { @@ -16,6 +19,15 @@ function setup(promise: Promise) { } } +function setupResourceType( + promise: Promise> +) { + return { + makeRequest: setupMakeRequest(promise), + entityMock: cloneMock('resourceType'), + } +} + describe('Entity ResourceProvider', () => { test('ResourceProvider is wrapped', async () => { return entityWrappedTest(setup, { wrapperMethod: wrapResourceProvider }) @@ -33,4 +45,38 @@ describe('Entity ResourceProvider', () => { wrapperMethod: wrapResourceProvider, }) }) + + test('API call upsertResourceType', async () => { + const { makeRequest, entityMock } = setupResourceType(new Promise((r) => r(resourceTypeMock))) + const entity = wrapResourceProvider(makeRequest, entityMock) + + const response = await entity['upsertResourceType']( + 'resourceProvider:resourceTypeId', + resourceTypeMock + ) + expect(response).to.deep.equal(resourceTypeMock) + expect(response.toPlainObject, 'response is wrapped').to.be.ok + }) + + test('API call getResourceType', async () => { + const { makeRequest, entityMock } = setupResourceType(new Promise((r) => r(resourceTypeMock))) + const entity = wrapResourceProvider(makeRequest, entityMock) + + const response = await entity['getResourceType']('resourceTypeId') + expect(response).to.deep.equal(resourceTypeMock) + expect(response.toPlainObject, 'response is wrapped').to.be.ok + }) + + test('API call getResourceTypes', async () => { + const { makeRequest, entityMock } = setupResourceType( + new Promise((r) => + r({ items: [resourceTypeMock], total: 1, skip: 0, limit: 100, sys: { type: 'Array' } }) + ) + ) + const entity = wrapResourceProvider(makeRequest, entityMock) + const response = await entity['getResourceTypes']() + + expect(response.items[0]).to.deep.equal(resourceTypeMock) + expect(response.items[0].toPlainObject, 'response is wrapped').to.be.ok + }) }) diff --git a/test/unit/entities/resource-type-test.ts b/test/unit/entities/resource-type-test.ts new file mode 100644 index 000000000..c23b2fd7f --- /dev/null +++ b/test/unit/entities/resource-type-test.ts @@ -0,0 +1,35 @@ +import { cloneMock } from '../mocks/entities' +import setupMakeRequest from '../mocks/makeRequest' +import { + entityActionTest, + entityWrappedTest, + entityDeleteTest, +} from '../test-creators/instance-entity-methods' +import { describe, test } from 'mocha' +import { wrapResourceType, type ResourceTypeProps } from '../../../lib/entities/resource-type' + +function setup(promise: Promise) { + return { + makeRequest: setupMakeRequest(promise), + entityMock: cloneMock('resourceType'), + } +} + +describe('Entity ResourceType', () => { + test('ResourceType is wrapped', async () => { + return entityWrappedTest(setup, { wrapperMethod: wrapResourceType }) + }) + + test('ResourceType upsert', async () => { + return entityActionTest(setup, { + wrapperMethod: wrapResourceType, + actionMethod: 'upsert', + }) + }) + + test('ResourceType delete', async () => { + return entityDeleteTest(setup, { + wrapperMethod: wrapResourceType, + }) + }) +}) diff --git a/test/unit/mocks/entities.js b/test/unit/mocks/entities.js index b97f5ec17..066eadd3e 100644 --- a/test/unit/mocks/entities.js +++ b/test/unit/mocks/entities.js @@ -1053,15 +1053,28 @@ export const userUIConfigMock = { const resourceProviderMock = { sys: Object.assign(cloneDeep(sysMock), { type: 'ResourceProvider', - organization: { - sys: { id: 'organization-id' }, - }, - appDefinition: { sys: { id: 'appDefinition-id' } }, + appDefinition: { sys: { id: 'appDefinition-id', linkType: 'AppDefinition', type: 'Link' } }, + organization: { sys: { id: 'organization-id', linkType: 'Organization', type: 'Link' } }, }), type: 'function', function: { sys: { id: 'function-id' } }, } +const resourceTypeMock = { + sys: Object.assign(cloneDeep(sysMock), { + type: 'ResourceType', + appDefinition: { sys: { id: 'appDefinition-id', linkType: 'AppDefinition', type: 'Link' } }, + organization: { sys: { id: 'organization-id', linkType: 'Organization', type: 'Link' } }, + resourceProvider: { + sys: { id: 'resourceProvider-id', linkType: 'ResourceProvider', type: 'Link' }, + }, + }), + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, +} + const mocks = { apiKey: apiKeyMock, appAction: appActionMock, @@ -1112,6 +1125,7 @@ const mocks = { releaseActionValidate: releaseActionValidateMock, releaseActionUnpublish: releaseActionUnpublishMock, resourceProvider: resourceProviderMock, + resourceType: resourceTypeMock, scheduledAction: scheduledActionMock, snapshot: snapShotMock, spaceMember: spaceMemberMock, @@ -1267,6 +1281,9 @@ function setupEntitiesMock(rewiredModuleApi) { resourceProvider: { wrapResourceProvider: sinon.stub(), }, + resourceType: { + wrapResourceType: sinon.stub(), + }, apiKey: { wrapApiKey: sinon.stub(), wrapApiKeyCollection: sinon.stub(), @@ -1414,4 +1431,5 @@ export { environmentTemplateValidationMock, taskMock, resourceProviderMock, + resourceTypeMock, } diff --git a/test/unit/plain/resource-type-test.ts b/test/unit/plain/resource-type-test.ts new file mode 100644 index 000000000..d7c1a693e --- /dev/null +++ b/test/unit/plain/resource-type-test.ts @@ -0,0 +1,80 @@ +import { expect } from 'chai' +import { describe, test } from 'mocha' +import sinon from 'sinon' +import { createClient } from '../../../lib/contentful-management' +import setupRestAdapter from '../adapters/REST/helpers/setupRestAdapter' +import { resourceTypeMock } from '../mocks/entities' + +describe('ResourceType', () => { + const organizationId = 'organizationId' + const appDefinitionId = 'appDefinitionId' + const resourceTypeId = 'resourceTypeId' + + test('get', async () => { + const { httpMock, adapterMock } = setupRestAdapter(Promise.resolve({ data: resourceTypeMock })) + const plainClient = createClient({ apiAdapter: adapterMock }, { type: 'plain' }) + const response = await plainClient.resourceType.get({ + organizationId, + appDefinitionId, + resourceTypeId, + }) + + expect(response).to.be.an('object') + expect(response.sys.id).to.equal('id') + + sinon.assert.calledWith( + httpMock.get, + `/organizations/organizationId/app_definitions/appDefinitionId/resource_provider/resource_types/resourceTypeId` + ) + }) + + test('getMany', async () => { + const { httpMock, adapterMock } = setupRestAdapter( + Promise.resolve({ data: { items: [resourceTypeMock] } }) + ) + const plainClient = createClient({ apiAdapter: adapterMock }, { type: 'plain' }) + const response = await plainClient.resourceType.getMany({ + organizationId, + appDefinitionId, + }) + + expect(response).to.be.an('object') + expect(response.items[0].sys.id).to.equal('id') + + sinon.assert.calledWith( + httpMock.get, + `/organizations/organizationId/app_definitions/appDefinitionId/resource_provider/resource_types` + ) + }) + + test('upsert', async () => { + const { httpMock, adapterMock } = setupRestAdapter(Promise.resolve({ data: resourceTypeMock })) + const plainClient = createClient({ apiAdapter: adapterMock }, { type: 'plain' }) + const response = await plainClient.resourceType.upsert( + { organizationId, appDefinitionId, resourceTypeId }, + { + name: 'resourceType', + defaultFieldMapping: { + title: 'title', + }, + } + ) + + expect(response).to.be.an('object') + expect(response.sys.id).to.equal('id') + + sinon.assert.calledWith( + httpMock.put, + `/organizations/organizationId/app_definitions/appDefinitionId/resource_provider/resource_types/resourceTypeId` + ) + }) + test('delete', async () => { + const { httpMock, adapterMock } = setupRestAdapter(Promise.resolve({ data: '' })) + const plainClient = createClient({ apiAdapter: adapterMock }, { type: 'plain' }) + await plainClient.resourceType.delete({ organizationId, appDefinitionId, resourceTypeId }) + sinon.assert.calledWith( + httpMock.delete, + `/organizations/organizationId/app_definitions/appDefinitionId/resource_provider/resource_types/resourceTypeId` + ) + }) +})