Skip to content

Commit b45cde9

Browse files
authored
[REMOCK-2] PLU-471: allow metadata in testRun, formsg testRun support preferMock metadata (#1061)
### Summary `testRun` functions in apps now support an additional param `testRunMetadata`. FormSG's `testRun` now accepts `preferMock` as part of `testRunMetadata` ### Changes - FormSG test run pulls last executionStep filtered by 'isMock' - `getLastExecutionStep` now supports an additional filter to the query builder - Added integration tests for the Step model's `getLastExecutionStep` method - Updated GraphQL schema to include `testRunMetadata` in the `ExecuteStepInput` - Modified the execute-step mutation to pass test run metadata to the test step service ### How to test? 1. Call the `executeStep` mutation directly with ``` { "stepId": "...", "testRunMetadata": { "preferMock": true/false } }
1 parent f4de954 commit b45cde9

File tree

13 files changed

+387
-48
lines changed

13 files changed

+387
-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

@@ -42,6 +59,117 @@ describe('new submission trigger', () => {
4259
} as unknown as IExecutionStep
4360
})
4461

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

packages/backend/src/helpers/global-variable.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const globalVariable = async (
9999
await step?.getLastExecutionStep({
100100
executionId: options?.sameExecution ? execution.id : undefined,
101101
testRunOnly: options?.testRunOnly,
102+
additionalFilter: options?.additionalFilter,
102103
})
103104
)?.toJSON()
104105
},

0 commit comments

Comments
 (0)