Skip to content

Commit 997e1da

Browse files
committed
feat: allow metadata in testRun, formsg testRun support preferMock metadata
1 parent ca7bd7c commit 997e1da

File tree

12 files changed

+385
-48
lines changed

12 files changed

+385
-48
lines changed

packages/backend/src/apps/formsg/__tests__/triggers/new-submission.test.ts

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,31 @@ import {
22
IDataOutMetadata,
33
IDataOutMetadatum,
44
IExecutionStep,
5+
IGlobalVariable,
56
IJSONObject,
67
} from '@plumber/types'
78

8-
import { beforeEach, describe, expect, it } from 'vitest'
9+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
910

1011
import { ADDRESS_LABELS } from '../../common/constants'
1112
import trigger from '../../triggers/new-submission'
1213

14+
const pushTriggerItemMock = vi.fn()
15+
const getLastExecutionStepMock = vi.fn()
16+
17+
const mocks = vi.hoisted(() => ({
18+
getMockData: vi.fn(),
19+
getFormDetailsFromGlobalVariable: vi.fn(),
20+
}))
21+
22+
vi.mock('../../triggers/new-submission/get-mock-data', () => ({
23+
default: mocks.getMockData,
24+
}))
25+
26+
vi.mock('../../common/webhook-settings', () => ({
27+
getFormDetailsFromGlobalVariable: mocks.getFormDetailsFromGlobalVariable,
28+
}))
29+
1330
describe('new submission trigger', () => {
1431
let executionStep: IExecutionStep
1532

@@ -37,6 +54,117 @@ describe('new submission trigger', () => {
3754
} as unknown as IExecutionStep
3855
})
3956

57+
describe('testRun', () => {
58+
const $ = {
59+
auth: { data: { formId: '123' } },
60+
user: { email: '[email protected]' },
61+
pushTriggerItem: pushTriggerItemMock,
62+
getLastExecutionStep: getLastExecutionStepMock,
63+
} as unknown as IGlobalVariable
64+
65+
const mockData = {
66+
formId: '123',
67+
responses: {
68+
mockTextFieldId: {
69+
question: 'What is your name?',
70+
answer: 'herp derp',
71+
},
72+
},
73+
}
74+
75+
const actualData = {
76+
formId: '123',
77+
responses: {
78+
textFieldId: {
79+
question: 'What is your age?',
80+
answer: 10,
81+
},
82+
},
83+
}
84+
85+
beforeEach(() => {
86+
mocks.getMockData.mockResolvedValue(mockData)
87+
mocks.getFormDetailsFromGlobalVariable.mockReturnValue({
88+
formId: '123',
89+
})
90+
})
91+
92+
afterEach(() => {
93+
vi.clearAllMocks()
94+
})
95+
96+
it('should use mock data if preferMock is true and there is no past submission', async () => {
97+
getLastExecutionStepMock.mockResolvedValue(null)
98+
await trigger.testRun($, { preferMock: true })
99+
expect(pushTriggerItemMock).toHaveBeenCalledWith({
100+
raw: mockData,
101+
meta: {
102+
internalId: '',
103+
},
104+
isMock: true,
105+
})
106+
})
107+
108+
it('should use mock data if preferMock is true even though there is past submission', async () => {
109+
getLastExecutionStepMock.mockResolvedValue({ dataOut: actualData })
110+
await trigger.testRun($, { preferMock: true })
111+
expect(pushTriggerItemMock).toHaveBeenCalledWith({
112+
raw: mockData,
113+
meta: {
114+
internalId: '',
115+
},
116+
isMock: true,
117+
})
118+
})
119+
120+
it('should use mock data if testRunMetadata is undefined and there is no past submission', async () => {
121+
getLastExecutionStepMock.mockResolvedValue(null)
122+
await trigger.testRun($, undefined)
123+
expect(pushTriggerItemMock).toHaveBeenCalledWith({
124+
raw: mockData,
125+
meta: {
126+
internalId: '',
127+
},
128+
isMock: true,
129+
})
130+
})
131+
132+
it('should use last test submission if testRunMetadata is undefined and there is past submission', async () => {
133+
getLastExecutionStepMock.mockResolvedValue({ dataOut: actualData })
134+
await trigger.testRun($, undefined)
135+
expect(pushTriggerItemMock).toHaveBeenCalledWith({
136+
raw: actualData,
137+
meta: {
138+
internalId: '',
139+
},
140+
isMock: false,
141+
})
142+
})
143+
144+
it('should use last test submission if preferMock is false and there is past submission', async () => {
145+
getLastExecutionStepMock.mockResolvedValue({ dataOut: actualData })
146+
await trigger.testRun($, { preferMock: false })
147+
expect(pushTriggerItemMock).toHaveBeenCalledWith({
148+
raw: actualData,
149+
meta: {
150+
internalId: '',
151+
},
152+
isMock: false,
153+
})
154+
})
155+
156+
it('should use mock data if preferMock is false and there is no past submission', async () => {
157+
getLastExecutionStepMock.mockResolvedValue(null)
158+
await trigger.testRun($, { preferMock: false })
159+
expect(pushTriggerItemMock).toHaveBeenCalledWith({
160+
raw: mockData,
161+
meta: {
162+
internalId: '',
163+
},
164+
isMock: true,
165+
})
166+
})
167+
})
40168
describe('dataOut metadata', () => {
41169
it('ensures that only question, answer and answerArray props are visible', async () => {
42170
const metadata = await trigger.getDataOutMetadata(executionStep)

packages/backend/src/apps/formsg/triggers/new-submission/index.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
import { IGlobalVariable, IRawTrigger } from '@plumber/types'
22

3+
import { RelatedQueryBuilder } from 'objection'
4+
import { z } from 'zod'
5+
36
import StepError from '@/errors/step'
7+
import ExecutionStep from '@/models/execution-step'
48

59
import { getFormDetailsFromGlobalVariable } from '../../common/webhook-settings'
610

711
import getDataOutMetadata from './get-data-out-metadata'
812
import getMockData from './get-mock-data'
913

14+
const formsgTestRunMetadataSchema = z
15+
.object({
16+
preferMock: z.boolean().optional(),
17+
})
18+
.default({ preferMock: false })
19+
1020
export const NricFilter = {
1121
None: 'none',
1222
Remove: 'remove',
@@ -59,7 +69,7 @@ const trigger: IRawTrigger = {
5969

6070
getDataOutMetadata,
6171

62-
async testRun($: IGlobalVariable) {
72+
async testRun($: IGlobalVariable, testRunMetadata?: { preferMock: boolean }) {
6373
if (!$.auth.data) {
6474
throw new StepError(
6575
'Missing FormSG connection',
@@ -69,28 +79,46 @@ const trigger: IRawTrigger = {
6979
)
7080
}
7181

82+
const testRunMetadataRes =
83+
formsgTestRunMetadataSchema.safeParse(testRunMetadata)
84+
if (!testRunMetadataRes.success) {
85+
throw new StepError(
86+
'Something went wrong',
87+
'Invalid test run metadata. Please refresh and try again.',
88+
$.step.position,
89+
$.app.name,
90+
)
91+
}
92+
7293
// data out should never be empty after test step is pressed once: either mock or actual data
7394
const { formId } = getFormDetailsFromGlobalVariable($)
74-
// We use last test execution step
75-
const lastTestExecutionStep = await $.getLastExecutionStep({
95+
// We use last actual submission test execution step execution step
96+
const lastSubmittedTestExecutionStep = await $.getLastExecutionStep({
7697
testRunOnly: true,
98+
additionalFilter: (qb: RelatedQueryBuilder<ExecutionStep>) =>
99+
qb.andWhereRaw("(metadata->>'isMock')::boolean IS DISTINCT FROM true"),
77100
})
78-
// If no past submission (no form) or the form is changed, it is a mock run (re-pull mock data)
101+
79102
const hasNoPastSubmission =
80-
lastTestExecutionStep?.dataOut?.formId !== formId ||
81-
lastTestExecutionStep.metadata.isMock
103+
!lastSubmittedTestExecutionStep ||
104+
lastSubmittedTestExecutionStep?.dataOut?.formId !== formId
105+
106+
const shouldUseMockData =
107+
hasNoPastSubmission || testRunMetadataRes.data.preferMock
108+
109+
// if test with mock data is selected OR no past submission exists
110+
// we use mock data
111+
const testData = shouldUseMockData
112+
? await getMockData($)
113+
: lastSubmittedTestExecutionStep?.dataOut
82114

83115
// if different or no form is detected, use mock data
84116
await $.pushTriggerItem({
85-
raw: hasNoPastSubmission
86-
? await getMockData($)
87-
: lastTestExecutionStep?.dataOut,
117+
raw: testData,
88118
meta: {
89119
internalId: '',
90120
},
91-
isMock:
92-
hasNoPastSubmission ||
93-
(lastTestExecutionStep.metadata?.isMock ?? false), // use previous mock run status from metadata by default
121+
isMock: shouldUseMockData,
94122
})
95123
},
96124
}

packages/backend/src/graphql/mutations/execute-step.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const executeStep: MutationResolvers['executeStep'] = async (
99
params,
1010
context,
1111
) => {
12-
const { stepId } = params.input
12+
const { stepId, testRunMetadata } = params.input
1313

1414
// Just checking for permissions here
1515
const stepToTest = await context.currentUser
@@ -20,6 +20,7 @@ const executeStep: MutationResolvers['executeStep'] = async (
2020

2121
const { executionStep, executionId } = await testStep({
2222
stepId: stepToTest.id,
23+
testRunMetadata,
2324
})
2425

2526
// Update flow to use the new test execution

packages/backend/src/graphql/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ input UpdateFlowConfigInput {
475475

476476
input ExecuteStepInput {
477477
stepId: String!
478+
testRunMetadata: JSONObject
478479
}
479480

480481
input DeleteFlowInput {

0 commit comments

Comments
 (0)