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
36 changes: 34 additions & 2 deletions src/TemplateArchiveProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,34 @@ export type InitResponse = {
state: State;
}

function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}

function validateTriggerResponse(result: unknown): asserts result is TriggerResponse {
if (!isObject(result)) {
throw new Error('Invalid trigger result: expected an object');
}
if (!isObject(result.result)) {
throw new Error('Invalid trigger result: missing result object');
}
if (!isObject(result.state)) {
throw new Error('Invalid trigger result: missing state object');
}
if (!Array.isArray(result.events)) {
throw new Error('Invalid trigger result: events must be an array');
}
}

function validateInitResponse(result: unknown): asserts result is InitResponse {
if (!isObject(result)) {
throw new Error('Invalid init result: expected an object');
}
if (!isObject(result.state)) {
throw new Error('Invalid init result: missing state object');
}
}

/**
* A template archive processor: can draft content using the
* templatemark for the archive and trigger the logic of the archive
Expand Down Expand Up @@ -124,7 +152,9 @@ export class TemplateArchiveProcessor {
arguments: [data, request, state, resolvedTime, resolvedOffset]
});
if(evalResponse.result) {
return evalResponse.result;
const executionResult = await evalResponse.result;
validateTriggerResponse(executionResult);
return executionResult;
}
else {
throw new Error('Trigger failed with message: ' + evalResponse.message);
Expand Down Expand Up @@ -175,7 +205,9 @@ export class TemplateArchiveProcessor {
arguments: [data, resolvedTime, resolvedOffset]
});
if(evalResponse.result) {
return evalResponse.result;
const executionResult = await evalResponse.result;
validateInitResponse(executionResult);
return executionResult;
}
else {
throw new Error('Init failed with message: ' + evalResponse.message);
Expand Down
126 changes: 60 additions & 66 deletions test/TemplateArchiveProcessor.test.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,58 @@
import {Template} from '@accordproject/cicero-core';
import { TemplateArchiveProcessor } from '../src/TemplateArchiveProcessor';
import { JavaScriptEvaluator } from '../src/JavaScriptEvaluator';

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', () => {
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 templateArchiveProcessor = await createTemplateArchiveProcessor();
const options = {};
const result = await templateArchiveProcessor.draft(data, 'markdown', options);
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 = await templateArchiveProcessor.init(data);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload:any = response.state;
expect(payload.count).toBe(0);
});

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);
Expand All @@ -100,4 +71,27 @@ describe('template archive processor', () => {
// the events should have been emitted
expect(payload.events[0].penaltyCalculated).toBe(true);
});

test.each([
[{ state: {}, events: [] }, 'Invalid trigger result: missing result object'],
[{ result: {}, events: [] }, 'Invalid trigger result: missing state object'],
[{ result: {}, state: {}, events: {} }, 'Invalid trigger result: events must be an array']
])('should validate malformed trigger result %j', async (triggerResult, errorMessage) => {
const templateArchiveProcessor = await createTemplateArchiveProcessor();
const stateResponse = await templateArchiveProcessor.init(data);
jest.spyOn(JavaScriptEvaluator.prototype, 'evalDangerously').mockResolvedValueOnce({
result: Promise.resolve(triggerResult)
});

await expect(templateArchiveProcessor.trigger(data, request, stateResponse.state)).rejects.toThrow(errorMessage);
});

test('should validate malformed init result', async () => {
const templateArchiveProcessor = await createTemplateArchiveProcessor();
jest.spyOn(JavaScriptEvaluator.prototype, 'evalDangerously').mockResolvedValueOnce({
result: Promise.resolve({})
});

await expect(templateArchiveProcessor.init(data)).rejects.toThrow('Invalid init result: missing state object');
});
});