/* Copyright (c) 2022 Betim Beja and Shko Online LLC Licensed under the MIT license. */ import type { SinonStub } from 'sinon'; import { stub } from 'sinon'; import { ODataQuery, parseOData } from '@shko.online/dataverse-odata'; import { MetadataDB } from '../ComponentFramework-Mock-Generator'; import { FormattingMock } from './Formatting.mock'; import { AttributeType } from './PropertyTypes'; import { ShkoOnline } from '../ShkoOnline'; import { DelayedPromise } from '../utils/DelayedPromise'; export class WebApiMock implements ComponentFramework.WebApi { _Delay: number = 200; _ConvertRowToOData: SinonStub<[row: any, entityMetadata: ShkoOnline.EntityMetadata], void>; createRecord: SinonStub< [entityType: string, data: ComponentFramework.WebApi.Entity], Promise >; deleteRecord: SinonStub<[entityType: string, id: string], Promise>; updateRecord: SinonStub< [entityType: string, id: string, data: ComponentFramework.WebApi.Entity], Promise >; retrieveMultipleRecords: SinonStub< [entityType: string, options?: string, maxPageSize?: number], Promise >; retrieveRecord: SinonStub< [entityType: string, id: string, options?: string], Promise >; constructor(db: MetadataDB, formatting: FormattingMock) { this._ConvertRowToOData = stub(); this._ConvertRowToOData.callsFake((row: any, entityMetadata: ShkoOnline.EntityMetadata) => { const oldRow = row; if (entityMetadata.Attributes) { entityMetadata.Attributes.forEach((attribute) => { const key = attribute.LogicalName; if ( /* attribute.AttributeType === AttributeType.Uniqueidentifier ||*/ attribute.AttributeType === AttributeType.Boolean ) { if (row[key] === undefined || row[key] === null) { delete row[key]; } } else if (attribute.AttributeType === AttributeType.Lookup) { const key = `_${attribute.LogicalName}_value`; const lookupValue = oldRow[key] as ComponentFramework.LookupValue; if (key in row) { row[key] = lookupValue?.id ?? null; if (lookupValue?.id) { row[`${key}@Microsoft.Dynamics.CRM.lookuplogicalname`] = lookupValue.entityType; if (lookupValue.name != null) { row[`${key}@OData.Community.Display.V1.FormattedValue`] = lookupValue.name; } if (oldRow[`${attribute.LogicalName}navigation`]) { row[`${key}@Microsoft.Dynamics.CRM.associatednavigationproperty`] = oldRow[`${attribute.LogicalName}navigation`]; } } } delete row[`${attribute.LogicalName}type`]; delete row[`${attribute.LogicalName}navigation`]; } else if (attribute.AttributeType === AttributeType.DateTime) { const dateValue = row[key] as Date; if (dateValue) { const YYYY = dateValue.getFullYear(); let MM = '0' + (dateValue.getMonth() + 1); MM = MM.substring(MM.length - 2); let DD = '0' + dateValue.getDate(); DD = DD.substring(DD.length - 2); let HH = '0' + dateValue.getUTCHours(); HH = HH.substring(HH.length - 2); let mm = '0' + dateValue.getMinutes(); mm = mm.substring(mm.length - 2); let ss = '0' + dateValue.getSeconds(); ss = ss.substring(ss.length - 2); row[key] = `${YYYY}-${MM}-${DD}T${HH}:${mm}:${ss}Z`; row[`${key}@OData.Community.Display.V1.FormattedValue`] = `${ dateValue.getUTCMonth() + 1 }/${dateValue.getUTCDate()}/${dateValue.getUTCFullYear()} ${ ((dateValue.getUTCHours() - 1) % 12) + 1 }:${dateValue.getUTCMinutes()} ${ dateValue.getUTCHours() > 12 || (dateValue.getUTCHours() == 12 && dateValue.getMinutes() > 0) ? 'PM' : 'AM' }`; } else { delete row[key]; } } else if ( attribute.AttributeType === AttributeType.Integer || attribute.AttributeType === AttributeType.BigInt ) { if (row[key] !== null && row[key] !== undefined) { row[key + '@OData.Community.Display.V1.FormattedValue'] = formatting.formatInteger( row[key], ); } } if (!!attribute.AttributeOf) { const original = entityMetadata.Attributes?.find( (att) => att.LogicalName == attribute.AttributeOf, ); if ( original?.AttributeType !== AttributeType.Lookup && oldRow[original?.LogicalName ?? ''] !== undefined && oldRow[original?.LogicalName ?? ''] !== null && oldRow[key] !== null && oldRow[key] !== undefined && original?.LogicalName! in row ) { row[original?.LogicalName! + '@OData.Community.Display.V1.FormattedValue'] = oldRow[key]; } delete row[key]; } }); } Object.getOwnPropertyNames(row).forEach((key) => { if (row[key] === undefined) { row[key] = null; } }); }); this.createRecord = stub(); this.createRecord.callsFake((entityType: string, data: ComponentFramework.WebApi.Entity) => { const metadata = db.getTableMetadata(entityType); if (!metadata) { return DelayedPromise.rejectAfterDelay( { message: `Entity ${entityType} does not exist.` }, this._Delay, ); } return DelayedPromise.resolveAfterDelay( { id: db.AddRow(entityType, data, metadata) || '', name: data[metadata.PrimaryNameAttribute || 'name'], entityType: entityType, }, this._Delay, ); }); this.deleteRecord = stub(); this.deleteRecord.callsFake((entityType: string, id: string) => { return new Promise((resolve, reject) => { setTimeout(() => { const result = db.GetRow(entityType, id); if (!result.entityMetadata) { return reject({ message: `Entity ${entityType} does not exist.` }); } if (!result.row) { return reject({ message: `Could not find record with id: '${id}' for entity: '${entityType}'.` }); } db.RemoveRow(entityType, id); resolve({ id, name: result.row?.[result.entityMetadata.PrimaryNameAttribute || 'name'], entityType, }); }, this._Delay); }); }); this.updateRecord = stub(); this.updateRecord.callsFake((entityType: string, id: string, data: ComponentFramework.WebApi.Entity) => { const attributeHandler = (attribute: ShkoOnline.AttributeMetadata) => { if (attribute.AttributeOf || attribute.AttributeType === AttributeType.Virtual) { return; } const key = attribute.AttributeType === AttributeType.Lookup ? `_${attribute.LogicalName}_value` : attribute.LogicalName; if (key in data) { db.UpdateValue(data[key], entityType, key, id); } }; const metadata = db.getTableMetadata(entityType); if (!metadata) { return DelayedPromise.rejectAfterDelay( `Entity ${entityType} does not exist.`, this._Delay, ); } metadata.Attributes?.forEach(attributeHandler); const result = db.GetRow(entityType, id); return DelayedPromise.resolveAfterDelay( { id, name: result.row?.[metadata.PrimaryNameAttribute || 'name'], entityType, }, this._Delay, ); }); this.retrieveMultipleRecords = stub(); this.retrieveMultipleRecords.callsFake((entityType: string, options?: string, maxPageSize?: number) => { return new Promise((resolve, reject) => { const parsed = options ? parseOData(options) : ({} as ODataQuery); if (parsed.error) { reject(parsed.error); } const entityMetadata = db.getTableMetadata(entityType); if (!entityMetadata) { reject(`Table ${entityType} does not exist`); return; } if (parsed.userQuery) { const userQuery = db.GetRow('userquery', parsed.userQuery); if (!userQuery.row) { reject({ code: '0x80040217', message: `Entity 'userquery' With Id = ${parsed.userQuery} Does Not Exist`, }); return; } if (userQuery.row['returnedtypecode'] !== entityMetadata.LogicalName) { reject({ code: '0x80060888', message: 'No Query View exists with the Given Query Id on the Entity Set.', }); return; } const parsedUserQuery = parseOData('?fetchXml=' + encodeURIComponent(userQuery.row['fetchxml'])); if (!parsedUserQuery.fetchXml) { throw new Error(`User Query with id ${parsed.userQuery} contains wrong data`); } const entities = db.SelectUsingFetchXml(parsedUserQuery.fetchXml); resolve({ entities, nextLink: 'next', }); return; } if (parsed.savedQuery) { const savedQuery = db.GetRow('savedquery', parsed.savedQuery); if (!savedQuery.row) { reject({ code: '0x80040217', message: `Entity 'savedquery' With Id = ${parsed.savedQuery} Does Not Exist`, }); return; } if (savedQuery.row['returnedtypecode'] !== entityMetadata.LogicalName) { reject({ code: '0x80060888', message: 'No Query View exists with the Given Query Id on the Entity Set.', }); return; } const parsedSavedQuery = parseOData('?fetchXml=' + encodeURIComponent(savedQuery.row['fetchxml'])); if (!parsedSavedQuery.fetchXml) { throw new Error(`Saved Query with id ${parsed.savedQuery} contains wrong data`); } const entities = db.SelectUsingFetchXml(parsedSavedQuery.fetchXml); resolve({ entities, nextLink: 'next', }); return; } if (parsed.fetchXml) { const entities = db.SelectUsingFetchXml(parsed.fetchXml); resolve({ entities, nextLink: 'next', }); return; } const entities = db.SelectUsingOData(entityMetadata, parsed) as any[]; entities.forEach((row) => { this._ConvertRowToOData(row, entityMetadata); }); resolve({ entities, nextLink: 'string', }); }); }); this.retrieveRecord = stub(); this.retrieveRecord.callsFake((entityType: string, id: string, options?: string) => { return new Promise((resolve, reject) => { setTimeout(() => { const result = db.GetRowForAPI(entityType, id); if (!result.entityMetadata) { return reject({ message: `Entity ${entityType} does not exist.` }); } if (!result.row) { return reject({ message: `Could not find record with id: '${id}' for entity: '${entityType}'.`, }); } if (options) { var parsed = parseOData(options); if (parsed.$select) { const oldRow = result.row; result.row = {}; parsed.$select.forEach((attribute) => { result.row[attribute] = oldRow[attribute]; }); if ( result.entityMetadata.PrimaryIdAttribute !== undefined && !parsed.$select.includes(result.entityMetadata.PrimaryIdAttribute) ) { result.row[result.entityMetadata.PrimaryIdAttribute] = oldRow[result.entityMetadata.PrimaryIdAttribute]; } } } this._ConvertRowToOData(result.row, result.entityMetadata); resolve(result.row); }, this._Delay); }); }); } }