From ae9dbb45dffad156040116ab90c28e85a1996d72 Mon Sep 17 00:00:00 2001 From: Liam Grace Date: Thu, 6 Jul 2017 11:29:54 +0100 Subject: [PATCH 1/2] Added assetdeclaration unit test --- .../test/data/parser/assetdeclaration.systypename.cto | 5 +++++ .../test/introspect/assetdeclaration.js | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 packages/composer-common/test/data/parser/assetdeclaration.systypename.cto diff --git a/packages/composer-common/test/data/parser/assetdeclaration.systypename.cto b/packages/composer-common/test/data/parser/assetdeclaration.systypename.cto new file mode 100644 index 0000000000..e84793b08b --- /dev/null +++ b/packages/composer-common/test/data/parser/assetdeclaration.systypename.cto @@ -0,0 +1,5 @@ +namespace com.testing + +asset Asset identified by id { + o String id +} \ No newline at end of file diff --git a/packages/composer-common/test/introspect/assetdeclaration.js b/packages/composer-common/test/introspect/assetdeclaration.js index 92c0c961bc..1fe5cb41cc 100644 --- a/packages/composer-common/test/introspect/assetdeclaration.js +++ b/packages/composer-common/test/introspect/assetdeclaration.js @@ -14,6 +14,7 @@ 'use strict'; +const IllegalModelException = require('../../lib/introspect/illegalmodelexception'); const AssetDeclaration = require('../../lib/introspect/assetdeclaration'); const ClassDeclaration = require('../../lib/introspect/classdeclaration'); const ModelFile = require('../../lib/introspect/modelfile'); @@ -129,6 +130,16 @@ describe('AssetDeclaration', () => { asset.validate(); }).should.throw(/more than one field named/); }); + + it('should throw an an IllegalModelException if its not a System Type and is called Asset', () => { + let asset = loadLastAssetDeclaration('test/data/parser/assetdeclaration.systypename.cto'); + try { + asset.validate(); + } catch (err) { + err.should.be.an.instanceOf(IllegalModelException); + err.message.should.match(/Asset is a reserved type name./); + } + }); }); describe('#getSuperType', () => { From 3e4e5e88c6ab51cc1619d4d5cf002635bce4fd23 Mon Sep 17 00:00:00 2001 From: Liam Grace Date: Thu, 6 Jul 2017 15:53:37 +0100 Subject: [PATCH 2/2] composer-common/lib/introspect unit tests --- .../lib/introspect/relationshipdeclaration.js | 3 - .../classdeclaration.isrelationshiptarget.cto | 3 + .../parser/classdeclaration.noidentifier.cto | 4 + ...claration.participantwithparents.child.cto | 6 +- .../parser/eventdeclaration.systypename.cto | 5 + .../participantdeclaration.systypename.cto | 5 + .../parser/participantdeclaration.valid.cto | 5 + .../relationshipdeclaration.missingtype.cto | 6 ++ .../test/introspect/basemodelexception.js | 5 + .../test/introspect/classdeclaration.js | 48 +++++++++ .../test/introspect/eventdeclaration.js | 43 ++++++++ .../test/introspect/modelfile.js | 35 +++++++ .../test/introspect/numbervalidator.js | 12 +++ .../test/introspect/participantdeclaration.js | 97 +++++++++++++++++++ .../introspect/relationshipdeclaration.js | 58 ++++++++++- .../test/introspect/transactiondeclaration.js | 65 +++++++++++++ 16 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto create mode 100644 packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto create mode 100644 packages/composer-common/test/data/parser/eventdeclaration.systypename.cto create mode 100644 packages/composer-common/test/data/parser/participantdeclaration.systypename.cto create mode 100644 packages/composer-common/test/data/parser/participantdeclaration.valid.cto create mode 100644 packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto create mode 100644 packages/composer-common/test/introspect/participantdeclaration.js create mode 100644 packages/composer-common/test/introspect/transactiondeclaration.js diff --git a/packages/composer-common/lib/introspect/relationshipdeclaration.js b/packages/composer-common/lib/introspect/relationshipdeclaration.js index da092b8743..eeeac24e60 100644 --- a/packages/composer-common/lib/introspect/relationshipdeclaration.js +++ b/packages/composer-common/lib/introspect/relationshipdeclaration.js @@ -77,11 +77,8 @@ class RelationshipDeclaration extends Property { throw new IllegalModelException('Relationship ' + this.getName() + ' points to a missing type ' + this.getFullyQualifiedTypeName(), classDecl.getModelFile(), this.ast.location); } - if ((namespace === ModelUtil.getSystemNamespace()) && classDecl.isEvent()) { // Transaction relationship in event, continue - } else if((namespace === ModelUtil.getSystemNamespace()) && classDeclaration.isSystemRelationshipTarget() === false) { - throw new IllegalModelException('Relationship ' + this.getName() + ' must be to an asset, participant or transaction, but is to ' + this.getFullyQualifiedTypeName(), classDecl.getModelFile(), this.ast.location); } else if(classDeclaration.isRelationshipTarget() === false) { throw new IllegalModelException('Relationship ' + this.getName() + ' must be to an asset or participant, but is to ' + this.getFullyQualifiedTypeName(), classDecl.getModelFile(), this.ast.location); } diff --git a/packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto b/packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto new file mode 100644 index 0000000000..13f26ac52b --- /dev/null +++ b/packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto @@ -0,0 +1,3 @@ +namespace com.testing +concept Test { +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto b/packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto new file mode 100644 index 0000000000..15f988d565 --- /dev/null +++ b/packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto @@ -0,0 +1,4 @@ +namespace com.testing + +transaction Transaction { +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto b/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto index 2559bb7579..dd533027ce 100644 --- a/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto +++ b/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto @@ -4,4 +4,8 @@ import com.testing.parent.Super participant Sub extends Super { } -participant Sub2 extends Super { } \ No newline at end of file +participant Sub2 extends Super { } + +concept Test { + +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/eventdeclaration.systypename.cto b/packages/composer-common/test/data/parser/eventdeclaration.systypename.cto new file mode 100644 index 0000000000..0bf429402c --- /dev/null +++ b/packages/composer-common/test/data/parser/eventdeclaration.systypename.cto @@ -0,0 +1,5 @@ +namespace com.testing + +event Event identified by eventId{ + o String eventId +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/participantdeclaration.systypename.cto b/packages/composer-common/test/data/parser/participantdeclaration.systypename.cto new file mode 100644 index 0000000000..a02505dd5d --- /dev/null +++ b/packages/composer-common/test/data/parser/participantdeclaration.systypename.cto @@ -0,0 +1,5 @@ +namespace com.testing + +participant Participant identified by participantId { + o String participantId +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/participantdeclaration.valid.cto b/packages/composer-common/test/data/parser/participantdeclaration.valid.cto new file mode 100644 index 0000000000..08e43751e0 --- /dev/null +++ b/packages/composer-common/test/data/parser/participantdeclaration.valid.cto @@ -0,0 +1,5 @@ +namespace com.testing + +participant P identified by pId { + o String pId +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto b/packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto new file mode 100644 index 0000000000..9f59b4937f --- /dev/null +++ b/packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto @@ -0,0 +1,6 @@ +namespace com.testing + +asset A identified by AId { + o String AId + --> Testing test +} \ No newline at end of file diff --git a/packages/composer-common/test/introspect/basemodelexception.js b/packages/composer-common/test/introspect/basemodelexception.js index a58db2a41a..d2269686e9 100644 --- a/packages/composer-common/test/introspect/basemodelexception.js +++ b/packages/composer-common/test/introspect/basemodelexception.js @@ -43,6 +43,11 @@ describe('BaseModelException', function () { exc.stack.should.be.a('string'); }); + it('should use messahe over fulMessage', () => { + let exc = new BaseModelException('message', {start: 1, end: 2}); + exc.message.should.equal('message'); + }); + it('should handle a lack of support for stack traces', function () { let captureStackTrace = Error.captureStackTrace; Error.captureStackTrace = null; diff --git a/packages/composer-common/test/introspect/classdeclaration.js b/packages/composer-common/test/introspect/classdeclaration.js index 3602620920..69e2f102a3 100644 --- a/packages/composer-common/test/introspect/classdeclaration.js +++ b/packages/composer-common/test/introspect/classdeclaration.js @@ -14,6 +14,7 @@ 'use strict'; +const IllegalModelException = require('../../lib/introspect/illegalmodelexception'); const ClassDeclaration = require('../../lib/introspect/classdeclaration'); const AssetDeclaration = require('../../lib/introspect/assetdeclaration'); const EnumDeclaration = require('../../lib/introspect/enumdeclaration'); @@ -139,6 +140,17 @@ describe('ClassDeclaration', () => { }).should.throw(/Duplicate class/); }); + it('should throw when not abstract, not enum and not concept without an identifier', () => { + let asset = loadLastDeclaration('test/data/parser/classdeclaration.noidentifier.cto', TransactionDeclaration); + asset.superType = null; + try { + asset.validate(); + } catch (err) { + err.should.be.an.instanceOf(IllegalModelException); + should.exist(err.message); + err.message.should.match(/Class Transaction is not declared as abstract. It must define an identifying field./); + } + }); }); describe('#accept', () => { @@ -309,4 +321,40 @@ describe('ClassDeclaration', () => { }); }); + describe('#isEvent', () => { + const modelFileNames = [ + 'test/data/parser/classdeclaration.participantwithparents.parent.cto', + 'test/data/parser/classdeclaration.participantwithparents.child.cto' + ]; + let modelManager; + + beforeEach(() => { + modelManager = new ModelManager(); + const modelFiles = loadModelFiles(modelFileNames, modelManager); + modelManager.addModelFiles(modelFiles); + }); + it('should return false', () => { + const testClass = modelManager.getType('com.testing.child.Sub'); + testClass.isEvent().should.be.false; + + }); + }); + + describe('#isRelationshipTarget', () => { + const modelFileNames = [ + 'test/data/parser/classdeclaration.isrelationshiptarget.cto', + ]; + let modelManager; + + beforeEach(() => { + modelManager = new ModelManager(); + const modelFiles = loadModelFiles(modelFileNames, modelManager); + modelManager.addModelFiles(modelFiles); + }); + it('should return false', () => { + const testClass = modelManager.getType('com.testing.Test'); + testClass.isRelationshipTarget().should.be.false; + + }); + }); }); diff --git a/packages/composer-common/test/introspect/eventdeclaration.js b/packages/composer-common/test/introspect/eventdeclaration.js index 0509e0c22f..eb555aa9ee 100644 --- a/packages/composer-common/test/introspect/eventdeclaration.js +++ b/packages/composer-common/test/introspect/eventdeclaration.js @@ -24,10 +24,44 @@ const sinon = require('sinon'); describe('EventDeclaration', () => { + let mockModelManager; + let mockSystemEvent; + + /** + * Load an arbitrary number of model files. + * @param {String[]} modelFileNames array of model file names. + * @param {ModelManager} modelManager the model manager to which the created model files will be registered. + * @return {ModelFile[]} array of loaded model files, matching the supplied arguments. + */ + const loadModelFiles = (modelFileNames, modelManager) => { + const modelFiles = []; + for (let modelFileName of modelFileNames) { + const modelDefinitions = fs.readFileSync(modelFileName, 'utf8'); + const modelFile = new ModelFile(modelManager, modelDefinitions); + modelFiles.push(modelFile); + } + modelManager.addModelFiles(modelFiles, modelFileNames); + return modelFiles; + }; + + const loadModelFile = (modelFileName) => { + return loadModelFiles([modelFileName], mockModelManager)[0]; + }; + + const loadLastDeclaration = (modelFileName, type) => { + const modelFile = loadModelFile(modelFileName); + const declarations = modelFile.getDeclarations(type); + return declarations[declarations.length - 1]; + }; + let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); + mockModelManager = sinon.createStubInstance(ModelManager); + mockSystemEvent = sinon.createStubInstance(EventDeclaration); + mockSystemEvent.getFullyQualifiedName.returns('org.hyperledger.composer.system.Event'); + mockModelManager.getSystemTypes.returns([mockSystemEvent]); }); afterEach(() => { @@ -50,6 +84,15 @@ describe('EventDeclaration', () => { }); }); + describe('#validate', () => { + it('should throw if event is not a system type but named event', () => { + let event = loadLastDeclaration('test/data/parser/eventdeclaration.systypename.cto', EventDeclaration); + event.superType = null; + (() => { + event.validate(); + }).should.throw(/Event is a reserved type name./); + }); + }); describe('#parse', () => { diff --git a/packages/composer-common/test/introspect/modelfile.js b/packages/composer-common/test/introspect/modelfile.js index 3a47a76496..e19d538aed 100644 --- a/packages/composer-common/test/introspect/modelfile.js +++ b/packages/composer-common/test/introspect/modelfile.js @@ -466,6 +466,17 @@ describe('ModelFile', () => { mf.getType('String').should.equal('String'); }); + it('should return false if imported, non primative\'s modelFile doesn\'t exist', () => { + const ast = { + namespace: 'org.acme', + body: [ ] + }; + sandbox.stub(parser, 'parse').returns(ast); + let mf = new ModelFile(mockModelManager, 'fake'); + mf.isImportedType = () => { return true; }; + mf.resolveImport = () => { return 'org.acme'; }; + should.not.exist(mf.getType('TNTAsset')); + }); }); describe('#getAssetDeclaration', () => { @@ -542,4 +553,28 @@ describe('ModelFile', () => { }); + describe('#getFullyQualifiedTypeName', () => { + it('should return null if not prmative, imported or local type', () => { + const ast = { + namespace: 'org.acme', + body: [ ] + }; + sandbox.stub(parser, 'parse').returns(ast); + let mf = new ModelFile(mockModelManager, 'fake'); + mf.isImportedType = () => { return false; }; + mf.isLocalType = () => { return false; }; + should.not.exist(mf.getFullyQualifiedTypeName('TNTAsset')); + }); + + it('should return the type name if its a primative type', () => { + const ast = { + namespace: 'org.acme', + body: [ ] + }; + sandbox.stub(parser, 'parse').returns(ast); + let modelFile = new ModelFile(mockModelManager, 'something'); + + modelFile.getFullyQualifiedTypeName('String').should.equal('String'); + }); + }); }); diff --git a/packages/composer-common/test/introspect/numbervalidator.js b/packages/composer-common/test/introspect/numbervalidator.js index 8f00224895..4fd1425372 100644 --- a/packages/composer-common/test/introspect/numbervalidator.js +++ b/packages/composer-common/test/introspect/numbervalidator.js @@ -114,5 +114,17 @@ describe('NumberValidator', () => { v.validate('id', 101); }).should.throw(/org.acme.myField: Value is outside upper bound 101/); }); + + it('should do nothing if no value is given', () => { + let v = new NumberValidator(mockField, VALID_UPPER_AND_LOWER_BOUND_AST); + v.validate(); + }); + }); + + describe('#toString', () => { + it('should return the correct string', () => { + let v = new NumberValidator(mockField, VALID_UPPER_AND_LOWER_BOUND_AST); + v.toString().should.equal(`NumberValidator lower: ${VALID_UPPER_AND_LOWER_BOUND_AST.lower} upper: ${VALID_UPPER_AND_LOWER_BOUND_AST.upper}`); + }); }); }); diff --git a/packages/composer-common/test/introspect/participantdeclaration.js b/packages/composer-common/test/introspect/participantdeclaration.js new file mode 100644 index 0000000000..158a040626 --- /dev/null +++ b/packages/composer-common/test/introspect/participantdeclaration.js @@ -0,0 +1,97 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const ParticipantDeclaration = require('../../lib/introspect/participantdeclaration'); +const ClassDeclaration = require('../../lib/introspect/classdeclaration'); +const ModelFile = require('../../lib/introspect/modelfile'); +const ModelManager = require('../../lib/modelmanager'); +const fs = require('fs'); + +require('chai').should(); +const sinon = require('sinon'); + +describe('ParticipantDeclaration', () => { + + let mockModelManager; + let mockClassDeclaration; + let mockSystemParticipant; + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + mockModelManager = sinon.createStubInstance(ModelManager); + mockSystemParticipant = sinon.createStubInstance(ParticipantDeclaration); + mockSystemParticipant.getFullyQualifiedName.returns('org.hyperledger.composer.system.Participant'); + mockModelManager.getSystemTypes.returns([mockSystemParticipant]); + mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + mockModelManager.getType.returns(mockClassDeclaration); + mockClassDeclaration.getProperties.returns([]); + }); + + afterEach(() => { + sandbox.restore(); + }); + + let loadParticipantDeclaration = (modelFileName) => { + let modelDefinitions = fs.readFileSync(modelFileName, 'utf8'); + let modelFile = new ModelFile(mockModelManager, modelDefinitions); + let assets = modelFile.getParticipantDeclarations(); + assets.should.have.lengthOf(1); + + return assets[0]; + }; + + describe('#constructor', () => { + + it('should throw if modelFile not specified', () => { + (() => { + new ParticipantDeclaration(null, {}); + }).should.throw(/required/); + }); + + it('should throw if ast not specified', () => { + let mockModelFile = sinon.createStubInstance(ModelFile); + (() => { + new ParticipantDeclaration(mockModelFile, null); + }).should.throw(/required/); + }); + + }); + + describe('#isRelationshipTarget', () => { + it('should return true', () => { + let p = loadParticipantDeclaration('test/data/parser/participantdeclaration.valid.cto'); + p.isRelationshipTarget().should.be.true; + }); + }); + + describe('#getSystemType', () => { + it('should return Participant', () => { + let p = loadParticipantDeclaration('test/data/parser/participantdeclaration.valid.cto'); + p.getSystemType().should.equal('Participant'); + }); + }); + + describe('#validate', () => { + it('should throw error if system type and name Participant', () => { + let p = loadParticipantDeclaration('test/data/parser/participantdeclaration.systypename.cto'); + (() => { + p.validate(); + }).should.throw(/Participant is a reserved type name./); + }); + }); + +}); diff --git a/packages/composer-common/test/introspect/relationshipdeclaration.js b/packages/composer-common/test/introspect/relationshipdeclaration.js index df4c490cec..4c7e5b9ecd 100644 --- a/packages/composer-common/test/introspect/relationshipdeclaration.js +++ b/packages/composer-common/test/introspect/relationshipdeclaration.js @@ -16,6 +16,7 @@ const ModelManager = require('../../lib/modelmanager'); const sinon = require('sinon'); +const ClassDeclaration = require('../../lib/introspect/classdeclaration'); const RelationshipDeclaration = require('../../lib/introspect/relationshipdeclaration'); // const ModelUtil = require('../../lib/modelutil'); @@ -26,6 +27,7 @@ chai.use(require('chai-things')); describe('RelationshipDeclaration', function () { let modelManager; + let mockClassDeclaration; const levelOneModel = `namespace org.acme.l1 participant Person identified by ssn { @@ -38,11 +40,11 @@ describe('RelationshipDeclaration', function () { `; - before(function () { + beforeEach(function () { modelManager = new ModelManager(); - }); + mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + mockClassDeclaration.getModelFile.returns(mockClassDeclaration); - beforeEach(function () { }); afterEach(function () { @@ -60,11 +62,59 @@ describe('RelationshipDeclaration', function () { (function () { field.validate(vehicleDeclaration); }).should.throw(/Relationship must have a type/); + }); + it('should throw if relationship points to a missing type', () => { + const model = ` + namespace org.acme.l1 + participant Person identified by ssn { + o String ssn + } + `; + const model2 = ` + namespace org.acme.l2 + import org.acme.l1.* + + asset Car identified by vin { + o String vin + -->Person owner + } + `; + + modelManager.addModelFile(model); + modelManager.addModelFile(model2); + const vehicleDeclaration = modelManager.getType('org.acme.l2.Car'); + const field = vehicleDeclaration.getProperty('owner'); + (field instanceof RelationshipDeclaration).should.be.true; + modelManager.getType = () => { return null; }; + (function () { + field.validate(vehicleDeclaration); + }).should.throw(/Relationship owner points to a missing type org.acme.l1./); }); + it('should throw if relationship is not a relationship target', () => { + const model = ` + namespace org.acme.l1 + participant Person identified by ssn { + o String ssn + } + + asset Car identified by vin { + o String vin + -->Person owner + } + `; + modelManager.addModelFile(model); + const vehicleDeclaration = modelManager.getType('org.acme.l1.Car'); + const field = vehicleDeclaration.getProperty('owner'); + (field instanceof RelationshipDeclaration).should.be.true; + mockClassDeclaration.isRelationshipTarget.returns(false); + field.getParent().getModelFile().getType = () => {return mockClassDeclaration;}; - + (function () { + field.validate(vehicleDeclaration); + }).should.throw(/Relationship owner must be to an asset or participant/); + }); }); }); diff --git a/packages/composer-common/test/introspect/transactiondeclaration.js b/packages/composer-common/test/introspect/transactiondeclaration.js new file mode 100644 index 0000000000..e384e121f5 --- /dev/null +++ b/packages/composer-common/test/introspect/transactiondeclaration.js @@ -0,0 +1,65 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const TransactionDeclaration = require('../../lib/introspect/transactiondeclaration'); +const ClassDeclaration = require('../../lib/introspect/classdeclaration'); +const ModelFile = require('../../lib/introspect/modelfile'); +const ModelManager = require('../../lib/modelmanager'); + +require('chai').should(); +const sinon = require('sinon'); + +describe('TransactionDeclaration', () => { + + let mockModelManager; + let mockSystemTransaction; + let mockClassDeclaration; + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + mockModelManager = sinon.createStubInstance(ModelManager); + mockSystemTransaction = sinon.createStubInstance(TransactionDeclaration); + mockSystemTransaction.getFullyQualifiedName.returns('org.hyperledger.composer.system.Transaction'); + mockModelManager.getSystemTypes.returns([mockSystemTransaction]); + mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + mockModelManager.getType.returns(mockClassDeclaration); + mockClassDeclaration.getProperties.returns([]); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('#validate', () => { + it('should throw error name is Transaction', () => { + const model = ` + namespace com.test + + transaction Transaction identified by transactionId { + o String transactionId + }`; + + const modelFile = new ModelFile(mockModelManager, model); + const p = modelFile.getTransactionDeclarations()[0]; + + (() => { + p.validate(); + }).should.throw(/Transaction is a reserved type name./); + }); + }); + +});