@shko.online/componentframework-mock
Version:
Mocking library to help with testing PowerApps Component Framework Components
341 lines (314 loc) • 16.3 kB
text/typescript
/*
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';
export class WebApiMock implements ComponentFramework.WebApi {
_Delay: number;
_ConvertRowToOData: SinonStub<[row: any, entityMetadata: ShkoOnline.EntityMetadata], void>;
createRecord: SinonStub<
[entityType: string, data: ComponentFramework.WebApi.Entity],
Promise<ComponentFramework.LookupValue>
>;
deleteRecord: SinonStub<[entityType: string, id: string], Promise<ComponentFramework.LookupValue>>;
updateRecord: SinonStub<
[entityType: string, id: string, data: ComponentFramework.WebApi.Entity],
Promise<ComponentFramework.LookupValue>
>;
retrieveMultipleRecords: SinonStub<
[entityType: string, options?: string, maxPageSize?: number],
Promise<ComponentFramework.WebApi.RetrieveMultipleResponse>
>;
retrieveRecord: SinonStub<
[entityType: string, id: string, options?: string],
Promise<ComponentFramework.WebApi.Entity>
>;
constructor(db: MetadataDB, formatting: FormattingMock) {
this._Delay = 200;
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 && lookupValue.id ? lookupValue.id : null;
if (lookupValue && 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) {
var original = entityMetadata.Attributes?.find(
(att) => att.LogicalName == attribute.AttributeOf,
);
if (
original?.AttributeType !== AttributeType.Lookup &&
oldRow[original?.LogicalName as string] !== undefined &&
oldRow[original?.LogicalName as string] !== null &&
oldRow[key] !== null &&
oldRow[key] !== undefined &&
(original?.LogicalName as string) 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) => {
return new Promise<ComponentFramework.LookupValue>((resolve, reject) => {
setTimeout(() => {
const metadata = db.getTableMetadata(entityType);
if (!metadata) {
return reject({ message: `Entity ${entityType} does not exist.` });
}
resolve({
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<ComponentFramework.LookupValue>((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) => {
return new Promise<ComponentFramework.LookupValue>((resolve, reject) => {
setTimeout(() => {
const metadata = db.getTableMetadata(entityType);
if (!metadata) {
return reject({ message: `Entity ${entityType} does not exist.` });
}
metadata.Attributes?.forEach((attribute) => {
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);
}
});
var result = db.GetRow(entityType, id);
resolve({
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<ComponentFramework.WebApi.RetrieveMultipleResponse>((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<ComponentFramework.WebApi.Entity>((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);
});
});
}
}