UNPKG

@eclipse-emfcloud/model-service

Version:
293 lines 13.4 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2023-2024 STMicroelectronics. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: MIT License which is // available at https://opensource.org/licenses/MIT. // // SPDX-License-Identifier: EPL-2.0 OR MIT // ***************************************************************************** var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable @typescript-eslint/no-non-null-assertion */ const chai_1 = __importStar(require("chai")); const chai_like_1 = __importDefault(require("chai-like")); const chai_things_1 = __importDefault(require("chai-things")); const sinon_1 = __importDefault(require("sinon")); const sinon_chai_1 = __importDefault(require("sinon-chai")); const model_manager_1 = require("@eclipse-emfcloud/model-manager"); const trigger_engine_1 = require("@eclipse-emfcloud/trigger-engine"); const fast_json_patch_1 = require("fast-json-patch"); const lodash_1 = require("lodash"); const model_service_model_manager_1 = require("../model-service-model-manager"); chai_1.default.use(chai_like_1.default); chai_1.default.use(chai_things_1.default); chai_1.default.use(sinon_chai_1.default); const alice = { name: 'Alice', id: '88BB5693-D51C-4BAE-9CC0-197E51304390', registeredOn: '19-07-2023', }; const bob = { name: 'Bob', id: 'A713B4E9-E5D5-4F23-8FF2-FD39800A08D5' }; const modelAImage = { name: 'Customer Data', lastRegistration: alice.registeredOn, customers: [alice], }; const keyboard = { sku: '11342720', name: 'keyboard' }; const mouse = { sku: '11855065', name: 'mouse' }; const aliceKeyboard = { customer: alice.id, sku: keyboard.sku, date: '24-07-2023', }; const bobMouse = { customer: bob.id, sku: mouse.sku }; const modelBImage = { name: 'Business Data', lastOrder: aliceKeyboard.date, products: [keyboard, mouse], orders: [aliceKeyboard], }; const MODEL_A_ID = 'test://38BCC304-652F-4C27-A225-81BF6C8EDCA8/model'; const MODEL_B_ID = 'test://4CE1EB0C-99AA-4ABE-B531-7618ED1174DC/model'; const MODEL_A2_ID = 'test://7D0A76E0-BB74-4F99-9FEF-D2A66874D785/model'; describe('ModelServiceModelManager', () => { let modelManager; let commandStack; const getModelA = () => modelManager.getModel(MODEL_A_ID); const getModelB = () => modelManager.getModel(MODEL_B_ID); beforeEach(() => { const modelA = (0, lodash_1.cloneDeep)(modelAImage); const modelB = (0, lodash_1.cloneDeep)(modelBImage); modelManager = (0, model_service_model_manager_1.createModelServiceModelManager)(); modelManager.setModel(MODEL_A_ID, modelA); modelManager.setModel(MODEL_B_ID, modelB); modelManager.triggerEngine.addModelTrigger(customerRegistration); modelManager.triggerEngine.addModelTrigger(orderDate); commandStack = modelManager.getCommandStack('test'); }); describe('multiple models', () => { it('multiple models triggered', async () => { const addBob = (0, model_manager_1.createModelUpdaterCommand)('Add Bob', MODEL_A_ID, (model) => { model.customers.push((0, lodash_1.cloneDeep)(bob)); }); const orderMouse = (0, model_manager_1.createModelUpdaterCommand)('Order Mouse', MODEL_B_ID, (model) => { model.orders.push((0, lodash_1.cloneDeep)(bobMouse)); }); const commandResult = await commandStack.execute(new model_manager_1.CompoundCommandImpl('Add Bob and Order Mouse', addBob, orderMouse)); // Assertions on models (0, chai_1.expect)(getModelA().customers).to.contain.an.item.like({ ...bob, registeredOn: '24-07-2023', }); (0, chai_1.expect)(getModelB().orders).to.contain.an.item.like({ ...bobMouse, date: '24-07-2023', }); // Assertions on command result (0, chai_1.expect)(commandResult).to.exist; const triggerCommands = Array.from(commandResult.entries()).filter(([key, _]) => key.label === 'Apply triggers'); // One per model (0, chai_1.expect)(triggerCommands).to.have.length.at.least(2); (0, chai_1.expect)(triggerCommands).to.contain.an.item.like([ { modelId: MODEL_A_ID }, [ { op: 'add', path: '/customers/1/registeredOn' }, { op: 'test' }, { op: 'replace', path: '/lastRegistration' }, ], ]); // The ModelB trigger didn't have to update /lastOrder (0, chai_1.expect)(triggerCommands).to.contain.an.item.like([ { modelId: MODEL_B_ID }, [{ op: 'add', path: '/orders/1/date' }], ]); }); }); describe('change notifications', () => { it('notification includes trigger changes', async () => { const sub = modelManager.subscribe(MODEL_A_ID); const modelChanged = sinon_1.default.stub(); sub.onModelChanged = modelChanged; const addBob = (0, model_manager_1.createModelUpdaterCommand)('Add Bob', MODEL_A_ID, (model) => { model.customers.push((0, lodash_1.cloneDeep)(bob)); }); await commandStack.execute(addBob); sinon_1.default.assert.calledOnceWithMatch(modelChanged, MODEL_A_ID, getModelA(), [ // The initial addition of the object { op: 'add', path: '/customers/1', value: { name: 'Bob', id: 'A713B4E9-E5D5-4F23-8FF2-FD39800A08D5' }, }, // The trigger setting the registeredOn property of the new object { op: 'add', path: '/customers/1/registeredOn', value: '24-07-2023' }, sinon_1.default.match({ op: 'test' }), { op: 'replace', path: '/lastRegistration', value: '24-07-2023', }, ]); }); }); describe('load notifications', () => { let modelA2; beforeEach(() => { modelA2 = (0, lodash_1.cloneDeep)(modelAImage); }); it('notifies on model added', () => { const sub = modelManager.subscribe(); const modelLoaded = sinon_1.default.stub(); sub.onModelLoaded = modelLoaded; modelManager.setModel(MODEL_A2_ID, modelA2); sinon_1.default.assert.calledOnceWithExactly(modelLoaded, MODEL_A2_ID); }); }); describe('unload notifications', () => { let modelA2; beforeEach(() => { modelA2 = (0, lodash_1.cloneDeep)(modelAImage); modelManager.setModel(MODEL_A2_ID, modelA2); }); it('notifies on model removed', () => { const sub = modelManager.subscribe(); const modelUnloaded = sinon_1.default.stub(); sub.onModelUnloaded = modelUnloaded; const removed = modelManager.removeModel(MODEL_A2_ID); sinon_1.default.assert.calledOnceWithExactly(modelUnloaded, MODEL_A2_ID, removed); }); it("doesn't notify on absent model", () => { modelManager.removeModel(MODEL_A2_ID); const sub = modelManager.subscribe(); const modelUnloaded = sinon_1.default.stub(); sub.onModelUnloaded = modelUnloaded; modelManager.removeModel(MODEL_A2_ID); sinon_1.default.assert.notCalled(modelUnloaded); }); }); describe('Corner Cases', () => { let applyTriggers; let exposedCoreModelManager; let exposedModelManager; beforeEach(() => { exposedModelManager = modelManager; exposedCoreModelManager = exposedModelManager.delegate; const workingCopyManager = exposedCoreModelManager._modelStore; workingCopyManager.open([MODEL_A_ID, MODEL_A2_ID, MODEL_B_ID]); // Need it open to test triggers with it applyTriggers = (commandResult) => exposedCoreModelManager.applyTriggers(workingCopyManager, commandResult); }); it('correctly applies triggers to undefined command results', async () => { const result = applyTriggers(undefined); return (0, chai_1.expect)(result).eventually.not.to.exist; }); it('correctly processes triggers for result of command that has no model', async () => { const delta1 = [ { op: 'add', path: '/orders/0', value: aliceKeyboard }, ]; const badCommand = (0, model_manager_1.createModelPatchCommand)('test', MODEL_B_ID, () => delta1); sinon_1.default.stub(badCommand, 'modelId').value(undefined); getModelA().customers.push((0, lodash_1.cloneDeep)(bob)); const delta2 = [ { op: 'add', path: '/customers/1', value: bob }, ]; const goodCommand = (0, model_manager_1.createModelPatchCommand)('test', MODEL_A_ID, () => delta2); const triggeringResults = new Map([ [badCommand, delta1], [goodCommand, delta2], ]); const result = await applyTriggers(triggeringResults); (0, chai_1.expect)(result).to.exist; (0, chai_1.expect)(result).to.be.like({ label: 'Apply triggers', modelId: MODEL_A_ID, state: 'ready', }); }); it('tolerates CompoundCommands in the triggering command-result map', async () => { const delta1 = [ { op: 'add', path: '/orders/0', value: aliceKeyboard }, ]; const badCommand = new model_manager_1.CompoundCommandImpl('test'); getModelA().customers.push((0, lodash_1.cloneDeep)(bob)); const delta2 = [ { op: 'add', path: '/customers/1', value: bob }, ]; const goodCommand = (0, model_manager_1.createModelPatchCommand)('test', MODEL_A_ID, () => delta2); const triggeringResults = new Map([ [badCommand, delta1], [goodCommand, delta2], ]); const result = await applyTriggers(triggeringResults); (0, chai_1.expect)(result).to.exist; (0, chai_1.expect)(result).to.be.like({ label: 'Apply triggers', modelId: MODEL_A_ID, state: 'ready', }); }); }); }); function set(model, path, value) { const op = (0, fast_json_patch_1.getValueByPointer)(model, path) === undefined ? 'add' : 'replace'; return { op, path, value }; } /** A trigger that initializes the registration date of a newly added customer. */ const customerRegistration = { getPatch: async (modelId, model, delta) => { const op = (0, trigger_engine_1.addOperations)(delta)[0]; if (modelId == MODEL_A_ID && op && /^\/customers\/\d+$/.test(op.path)) { return [ set(model, `${op.path}/registeredOn`, '24-07-2023'), set(model, '/lastRegistration', '24-07-2023'), ]; } return undefined; }, }; /** A trigger that initializes the date of a newly added order. */ const orderDate = { getPatch: (modelId, model, delta) => { const op = (0, trigger_engine_1.addOrReplaceOperations)(delta)[0]; if (op && modelId == MODEL_B_ID && /^\/orders\/\d+$/.test(op.path)) { return [ set(model, `${op.path}/date`, '24-07-2023'), set(model, '/lastOrder', '24-07-2023'), ]; } return undefined; }, }; //# sourceMappingURL=model-service-model-manager.spec.js.map