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
22 changes: 22 additions & 0 deletions openapi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@
"pattern": "^urn:uuid:"
}
}
},
"userData": {
"type": "object",
"required": [
"personalisation",
"caseReference"
],
"properties": {
"personalisation": {
"type": "object"
},
"caseReference": {
"type": "string"
}
}
}
}
}
Expand All @@ -153,6 +168,13 @@
},
"external": {
"id": "urn:uuid:f81d4fae-7dec-11d0-a765-123456781234"
},
"userData": {
"personalisation": {
"first-name": "Test",
"surname": "Test case"
},
"caseReference": "123456"
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions openapi/src/json-schemas/api/_questionnaires/post/req/201.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@
"$ref": "../../../../models/definitions/external-id.json"
}
}
},
"userData": {
"type": "object",
"required": ["personalisation", "caseReference"],
"properties": {
"personalisation": {
"type": "object"
},
"caseReference": {
"type": "string"
}
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions openapi/src/openapi-src.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@
},
"external": {
"id": "urn:uuid:f81d4fae-7dec-11d0-a765-123456781234"
},
"userData": {
"personalisation": {
"first-name": "Test",
"surname": "Test case"
},
"caseReference": "123456"
}
}
}
Expand Down
44 changes: 42 additions & 2 deletions questionnaire/questionnaire-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ defaults.createQuestionnaireDAL = require('./questionnaire-dal');

defaults.apiVersion = '2023-05-17';

defaults.createTaskRunner = require('./questionnaire/utils/taskRunner');
const sendNotifyMessageToSQS = require('./questionnaire/utils/taskRunner/tasks/postToNotify');
const sequential = require('./questionnaire/utils/taskRunner/tasks/sequential');

function createQuestionnaireService({
logger,
apiVersion = defaults.apiVersion,
ownerId,
createQuestionnaireDAL = defaults.createQuestionnaireDAL
createQuestionnaireDAL = defaults.createQuestionnaireDAL,
createTaskRunner = defaults.createTaskRunner
} = {}) {
const db = createQuestionnaireDAL({logger, ownerId});

Expand Down Expand Up @@ -62,7 +67,13 @@ function createQuestionnaireService({
return false;
}

async function createQuestionnaire(templateName, ownerData, originData, externalData) {
async function createQuestionnaire(
templateName,
ownerData,
originData,
externalData,
userData
) {
if (!(templateName in templates)) {
throw new VError(
{
Expand Down Expand Up @@ -103,8 +114,37 @@ function createQuestionnaireService({
};
}

if (userData) {
questionnaire.meta.personalisation = userData.personalisation;
questionnaire.answers.system['case-reference'] = userData.caseReference;
}

await db.createQuestionnaire(uuidV4, questionnaire);

const taskImplementations = {
sendNotifyMessageToSQS
};

if (questionnaire.onCreate) {
const onCreateTaskDefinition = JSON.parse(JSON.stringify(questionnaire.onCreate));
const taskRunner = createTaskRunner({
taskImplementations: {
sequential,
...taskImplementations
},
context: {
logger,
questionnaireDef: questionnaire,
type: 'onCreate'
}
});
try {
await taskRunner.run(onCreateTaskDefinition);
} catch (error) {
logger.info(error);
}
}

if (ownerData.isAuthenticated) {
await updateExpiryForAuthenticatedOwner(uuidV4, ownerData.id);
}
Expand Down
63 changes: 62 additions & 1 deletion questionnaire/questionnaire-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ const failedSubmissionStatus = 'FAILED';
const originData = {
channel: 'telephone'
};
const onCreateTasks = {
id: 'task0',
type: 'sequential',
retries: 0,
data: [
{
id: 'task1',
type: 'sendNotifyMessageToSQS',
retries: 0,
data: {
questionnaire: '$.questionnaireDef',
logger: '$.logger'
}
}
]
};

beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -211,6 +227,13 @@ const mockDalService = require('./questionnaire-dal')();

const createQuestionnaireService = require('./questionnaire-service');

jest.mock('q-templates-application', () => {
return {
...jest.requireActual('q-templates-application'),
onCreate: onCreateTasks
};
});

describe('Questionnaire Service', () => {
describe('Unknown API version', () => {
describe('Questionnaire service is called with an incompatible API', () => {
Expand All @@ -227,7 +250,11 @@ describe('Questionnaire Service', () => {
});
describe('DCS API Version 2023-05-17', () => {
const questionnaireService = createQuestionnaireService({
logger: () => 'Logged from createQuestionnaire test',
logger: {
info: jest.fn(() => {
return 'Logged from createQuestionnaire test';
})
},
apiVersion,
ownerId
});
Expand Down Expand Up @@ -351,6 +378,40 @@ describe('Questionnaire Service', () => {
questionnaireService.createQuestionnaire(templatename, ownerData)
).rejects.toThrow('Owner data must be defined');
});

it('Should run any onCreate tasks defined in the questionnaire', async () => {
const runMock = jest.fn(() => 'ok!');
const questionnaireService = createQuestionnaireService({
logger: () => 'Logged from createQuestionnaire test',
apiVersion,
createTaskRunner: () => {
return {run: runMock};
}
});
await questionnaireService.createQuestionnaire(templatename, ownerData);
expect(runMock).toHaveBeenCalledWith(onCreateTasks);
});

it('Should log an error but still return if any onCreate tasks fail', async () => {
const failError = new Error('Task failed to run');
const runMock = jest.fn(() => {
throw failError;
});
const loggerMock = {info: jest.fn()};
const questionnaireService = createQuestionnaireService({
logger: loggerMock,
apiVersion,
createTaskRunner: () => {
return {run: runMock};
}
});

await expect(
questionnaireService.createQuestionnaire(templatename, ownerData)
).resolves.not.toThrow();
expect(runMock).toHaveBeenCalledWith(onCreateTasks);
expect(loggerMock.info).toHaveBeenCalledWith(failError);
});
});

describe('getProgressEntries', () => {
Expand Down
4 changes: 2 additions & 2 deletions questionnaire/questionnaire/questionnaire.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,8 @@ function createQuestionnaire({
return undefined;
}

function getPermittedActions() {
const actions = questionnaireDefinition?.meta?.onComplete?.actions;
function getPermittedActions(type = 'onComplete') {
const actions = questionnaireDefinition?.meta?.[type]?.actions;

if (actions) {
const answersAndRoles = {
Expand Down
39 changes: 39 additions & 0 deletions questionnaire/questionnaire/questionnaire.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1260,4 +1260,43 @@ describe('Questionnaire', () => {
});
});
});
describe('Given both "onComplete" and "onCreate" actions definitions', () => {
it('should return actions of the correct type', () => {
const questionnaire = createQuestionnaire({
questionnaireDefinition: {
meta: {
onComplete: {
actions: [
{
type: 'actionA'
},
{
type: 'actionD'
}
]
},
onCreate: {
actions: [
{
type: 'actionC'
},
{
type: 'actionB'
}
]
}
}
}
});
const completeActions = questionnaire.getPermittedActions('onComplete');
const completeActionTypes = completeActions.map(action => action.type);
expect(completeActionTypes.length).toEqual(2);
expect(completeActionTypes).toEqual(['actionA', 'actionD']);

const createActions = questionnaire.getPermittedActions('onCreate');
const createActionTypes = createActions.map(action => action.type);
expect(createActionTypes.length).toEqual(2);
expect(createActionTypes).toEqual(['actionC', 'actionB']);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
const createSqsService = require('../../../../../../services/sqs/index');
const createQuestionnaireHelper = require('../../../../questionnaire');

async function sendNotifyMessageToSQS({questionnaire, logger}) {
async function sendNotifyMessageToSQS({questionnaire, logger, type}) {
const questionnaireId = questionnaire.id;
logger.info(`Sending notify message to SQS for questionnaire with id ${questionnaireId}`);
const sqsService = createSqsService({logger});
const questionnaireDef = createQuestionnaireHelper({questionnaireDefinition: questionnaire});
const permittedActions = questionnaireDef.getPermittedActions();
const permittedActions = questionnaireDef.getPermittedActions(type);
let sqsResponse = {MessageId: 'MessageId'};

await Promise.all(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,26 @@ describe('Post to Notify task', () => {
expect(sendMock).toHaveBeenCalledTimes(1);
expect(mockLogger.info).toHaveBeenCalledTimes(1);
});

it('Should complete onCreate actions if the type is onCreate', async () => {
sendMock = mockSqsService.mockImplementation(() => ({
send: payload => payload
}));
const data = {
questionnaire: questionnaireWithEmail,
logger: mockLogger,
type: 'onCreate'
};
const messageResult = await sendNotifyMessageToSQS(data);
expect(sendMock).toHaveBeenCalledTimes(1);
expect(messageResult).toEqual({
reference: null,
templateId: '00000000-aaaa-0000-aaaa-000000000000',
emailAddress: 'foo@bar.com',
personalisation: {
caseReference: '12/34567',
content: undefined
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,22 @@
}
]
},
"onCreate": {
"actions": [
{
"data": {
"reference": null,
"templateId": "00000000-aaaa-0000-aaaa-000000000000",
"emailAddress": "foo@bar.com",
"personalisation": {
"caseReference": "12/34567"
}
},
"type": "sendEmail",
"description": "Notification email - applicant:adult"
}
]
},
"questionnaireDocumentVersion": "4.2.0"
},
"type": "apply-for-compensation",
Expand Down
3 changes: 2 additions & 1 deletion questionnaire/submissions/submissions-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ function createSubmissionService({
},
context: {
logger,
questionnaireDef: questionnaireDefinition
questionnaireDef: questionnaireDefinition,
type: 'onComplete'
}
});

Expand Down