Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 44 additions & 77 deletions test/TemplateArchiveProcessor.test.ts
Original file line number Diff line number Diff line change
@@ -1,110 +1,77 @@
import {Template} from '@accordproject/cicero-core';
import { Template } from '@accordproject/cicero-core';
import { TemplateArchiveProcessor, InitResponse, TriggerResponse } from '../src/TemplateArchiveProcessor';
import { mockExternalModelFetches } from './support/externalModels';

const data = {
"$class": "io.clause.latedeliveryandpenalty@0.1.0.TemplateModel",
"forceMajeure": true,
"penaltyDuration": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 2,
"unit": "days"
},
"penaltyPercentage": 10.5,
"capPercentage": 55,
"termination": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 15,
"unit": "days"
},
"fractionalPart": "days",
"clauseId": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8",
"$identifier": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8"
};

const request = {
goodsValue: 100
};

async function createTemplateArchiveProcessor() {
const template = await Template.fromDirectory('test/archives/latedeliveryandpenalty-typescript', { offline: true });
return new TemplateArchiveProcessor(template);
}

describe('template archive processor', () => {
beforeEach(() => {
mockExternalModelFetches();
});

afterEach(() => {
jest.restoreAllMocks();
});

test('should draft a template', async () => {
const template = await Template.fromDirectory('test/archives/latedeliveryandpenalty-typescript', {offline: true});
const templateArchiveProcessor = new TemplateArchiveProcessor(template);
const data = {
"$class": "io.clause.latedeliveryandpenalty@0.1.0.TemplateModel",
"forceMajeure": true,
"penaltyDuration": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 2,
"unit": "days"
},
"penaltyPercentage": 10.5,
"capPercentage": 55,
"termination": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 15,
"unit": "days"
},
"fractionalPart": "days",
"clauseId": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8",
"$identifier": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8"
};
const options = {};
const result = await templateArchiveProcessor.draft(data, 'markdown', options);
const templateArchiveProcessor = await createTemplateArchiveProcessor();
const result = await templateArchiveProcessor.draft(data, 'markdown', {});
expect(result).toMatchSnapshot();
});

test('should init a template', async () => {
const template = await Template.fromDirectory('test/archives/latedeliveryandpenalty-typescript', {offline: true});
const templateArchiveProcessor = new TemplateArchiveProcessor(template);
const data = {
"$class": "io.clause.latedeliveryandpenalty@0.1.0.TemplateModel",
"forceMajeure": true,
"penaltyDuration": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 2,
"unit": "days"
},
"penaltyPercentage": 10.5,
"capPercentage": 55,
"termination": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 15,
"unit": "days"
},
"fractionalPart": "days",
"clauseId": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8",
"$identifier": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8"
};
const templateArchiveProcessor = await createTemplateArchiveProcessor();
const response: InitResponse = await templateArchiveProcessor.init(data);
const payload = response.state as { count?: number };
expect(payload.count).toBe(0);
});

test('should compile logic', async () => {
const template = await Template.fromDirectory('test/archives/latedeliveryandpenalty-typescript', {offline: true});
const templateArchiveProcessor = new TemplateArchiveProcessor(template);
const templateArchiveProcessor = await createTemplateArchiveProcessor();
const compiledCode = await templateArchiveProcessor.compileLogic();
expect(compiledCode['logic/logic.ts']).toBeDefined();
expect(compiledCode['logic/logic.ts'].code).toContain('LateDeliveryAndPenalty');
});

test('should trigger a template', async () => {
const template = await Template.fromDirectory('test/archives/latedeliveryandpenalty-typescript', {offline: true});
const templateArchiveProcessor = new TemplateArchiveProcessor(template);
const data = {
"$class": "io.clause.latedeliveryandpenalty@0.1.0.TemplateModel",
"forceMajeure": true,
"penaltyDuration": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 2,
"unit": "days"
},
"penaltyPercentage": 10.5,
"capPercentage": 55,
"termination": {
"$class": "org.accordproject.time@0.3.0.Duration",
"amount": 15,
"unit": "days"
},
"fractionalPart": "days",
"clauseId": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8",
"$identifier": "c88e5ed7-c3e0-4249-a99c-ce9278684ac8"
};
const request = {
goodsValue: 100
};
const templateArchiveProcessor = await createTemplateArchiveProcessor();

// first we init the template
const stateResponse = await templateArchiveProcessor.init(data);

// then we trigger the template
const response: TriggerResponse = await templateArchiveProcessor.trigger(data, request, stateResponse.state);

// we should have a result
const resultPayload = response.result as { penalty?: number };
expect(resultPayload.penalty).toBe(2625);

// the state should have been updated
const statePayload = response.state as { count?: number };
expect(statePayload.count).toBe(1);

// the events should have been emitted
const eventPayload = response.events[0] as { penaltyCalculated?: boolean };
expect(eventPayload.penaltyCalculated).toBe(true);
});
Expand Down
3 changes: 2 additions & 1 deletion test/TemplateMarkInterpreter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TemplateMarkInterpreter } from '../src';
import { TemplateMarkTransformer } from '@accordproject/markdown-template';
import { readFileSync, readdirSync } from 'fs';
import * as path from 'path';
import { loadOfflineExternalModels } from './support/externalModels';

const CLAUSE_LIBRARY = {
'clauses': [
Expand Down Expand Up @@ -70,8 +71,8 @@ describe('templatemark interpreter', () => {
const data = JSON.parse(readFileSync(`${GOOD_TEMPLATES_ROOT}/${templateName}/data.json`, 'utf-8'));

const modelManager = new ModelManager();
loadOfflineExternalModels(modelManager);
modelManager.addCTOModel(model, undefined, true);
await modelManager.updateExternalModels();
const engine = new TemplateMarkInterpreter(modelManager, CLAUSE_LIBRARY);

const templateMarkTransformer = new TemplateMarkTransformer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.
*/

concerto version "^3.0.0"

namespace org.accordproject.party@0.2.0

/* A party to a contract */
participant Party identified by partyId {
o String partyId
}
34 changes: 34 additions & 0 deletions test/models/@models.accordproject.org.money@0.3.0.cto
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.
*/

concerto version "^3.0.0"

namespace org.accordproject.money@0.3.0

/**
* Represents an amount of money.
*/
concept MonetaryAmount {
o Double doubleValue
o CurrencyCode currencyCode
}

/**
* Currency codes required by the offline template fixtures.
*/
enum CurrencyCode {
o EUR
o GBP
o USD
}
36 changes: 36 additions & 0 deletions test/models/@models.accordproject.org.time@0.3.0.cto
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.
*/

concerto version "^3.0.0"

namespace org.accordproject.time@0.3.0

/**
* Units for a duration.
*/
enum TemporalUnit {
o seconds
o minutes
o hours
o days
o weeks
}

/**
* A duration. For example, 6 hours.
*/
concept Duration {
o Long amount
o TemporalUnit unit
}
46 changes: 46 additions & 0 deletions test/support/externalModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { readFileSync } from 'fs';
import path from 'path';
import { ModelManager } from '@accordproject/concerto-core';

const TEST_MODELS_DIR = path.resolve(__dirname, '..', 'models');
const ARCHIVE_MODELS_DIR = path.resolve(__dirname, '..', 'archives', 'latedeliveryandpenalty-typescript', 'model');

const VENDORED_MODEL_FILES = [
'@models.accordproject.org.money@0.3.0.cto',
'@models.accordproject.org.accordproject.party@0.2.0.cto',
'@models.accordproject.org.time@0.3.0.cto'
];

export function loadOfflineExternalModels(modelManager: ModelManager) {
VENDORED_MODEL_FILES.forEach((fileName) => {
const model = readFileSync(path.join(TEST_MODELS_DIR, fileName), 'utf-8');
modelManager.addCTOModel(model, fileName);
});
}

export function mockExternalModelFetches() {
const originalFetch = global.fetch.bind(global);
const modelByUrl = new Map([
['https://models.accordproject.org/money@0.3.0.cto', readFileSync(path.join(TEST_MODELS_DIR, '@models.accordproject.org.money@0.3.0.cto'), 'utf-8')],
['https://models.accordproject.org/accordproject/party@0.2.0.cto', readFileSync(path.join(TEST_MODELS_DIR, '@models.accordproject.org.accordproject.party@0.2.0.cto'), 'utf-8')],
['https://models.accordproject.org/time@0.3.0.cto', readFileSync(path.join(TEST_MODELS_DIR, '@models.accordproject.org.time@0.3.0.cto'), 'utf-8')],
['https://models.accordproject.org/accordproject/contract@0.2.0.cto', readFileSync(path.join(ARCHIVE_MODELS_DIR, '@models.accordproject.org.accordproject.contract@0.2.0.cto'), 'utf-8')],
['https://models.accordproject.org/accordproject/runtime@0.2.0.cto', readFileSync(path.join(ARCHIVE_MODELS_DIR, '@models.accordproject.org.accordproject.runtime@0.2.0.cto'), 'utf-8')]
]);

return jest.spyOn(global, 'fetch').mockImplementation(async (input: string | URL | Request, init?: RequestInit) => {
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
const model = modelByUrl.get(url);

if (model !== undefined) {
return new Response(model, {
status: 200,
headers: {
'content-type': 'text/plain'
}
});
}

return originalFetch(input, init);
});
}