Skip to content

Commit a1e59fb

Browse files
committed
fix issues and add tests for delete from s3
1 parent 8d89224 commit a1e59fb

File tree

5 files changed

+124
-9
lines changed

5 files changed

+124
-9
lines changed

packages/backend/src/graphql/__tests__/mutations/delete-from-s3.itest.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Flow from '@/models/flow'
66
import Context from '@/types/express/context'
77

88
import { generateMockContext } from './tiles/table.mock'
9+
import { generateMockFlow, generateMockStep } from './flow.mock'
910

1011
const mockFlowId = '8c2a70d1-e78b-431e-9069-a4d8f97883f6'
1112
const mockBucket = 'test-bucket'
@@ -38,6 +39,29 @@ vi.mock('@aws-sdk/client-s3', () => ({
3839
DeleteObjectsCommand: vi.fn(),
3940
}))
4041

42+
function createMockS3Id(mockFileName: string) {
43+
return `s3:${mockBucket}:${mockFlowId}/${mockFileName}.txt`
44+
}
45+
46+
async function createMockStep(
47+
context: Context,
48+
flowId: string,
49+
position: number,
50+
params: Record<string, any>,
51+
config: Record<string, any> = {},
52+
) {
53+
return generateMockStep(
54+
context,
55+
'sendTransactionalEmail',
56+
'postman',
57+
'action',
58+
flowId,
59+
position,
60+
params,
61+
config,
62+
)
63+
}
64+
4165
describe('deleteFromS3', () => {
4266
let context: Context
4367
beforeEach(async () => {
@@ -46,17 +70,35 @@ describe('deleteFromS3', () => {
4670
})
4771

4872
it('should successfully delete an object when user owns the flow', async () => {
49-
await Flow.query().insert({
50-
id: mockFlowId,
51-
name: 'Test Flow',
52-
userId: context.currentUser.id,
53-
})
73+
await generateMockFlow(context, mockFlowId)
5474

5575
await expect(deleteFromS3(null, { id: mockS3Id }, context)).resolves.toBe(
5676
true,
5777
)
5878
})
5979

80+
it('should delete object from all other steps within a flow', async () => {
81+
const fileToDelete = createMockS3Id('test_1')
82+
await generateMockFlow(context, mockFlowId)
83+
await createMockStep(context, mockFlowId, 2, {
84+
body: 'Test body',
85+
attachments: [fileToDelete],
86+
})
87+
await createMockStep(context, mockFlowId, 2, {
88+
body: 'Test body',
89+
attachments: [fileToDelete, createMockS3Id('test_2')],
90+
})
91+
92+
await deleteFromS3(null, { id: fileToDelete }, context)
93+
const postDeleteSteps = await context.currentUser
94+
.$relatedQuery('steps')
95+
.where({ flow_id: mockFlowId })
96+
97+
postDeleteSteps.forEach((step) => {
98+
expect(step.parameters.attachments).not.toContain(fileToDelete)
99+
})
100+
})
101+
60102
it('should throw an error if user does not have access to the flow', async () => {
61103
vi.fn()
62104
.mockRejectedValue(Flow.hasAccess)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Flow from '@/models/flow'
2+
import Step from '@/models/step'
3+
import Context from '@/types/express/context'
4+
5+
export async function generateMockFlow(
6+
context: Context,
7+
id: string,
8+
config?: Record<string, any>,
9+
) {
10+
await Flow.query().insert({
11+
id,
12+
name: 'Test Flow',
13+
userId: context.currentUser.id,
14+
config: config ?? {},
15+
})
16+
}
17+
18+
export async function generateMockStep(
19+
context: Context,
20+
key:
21+
| 'delayFor'
22+
| 'everyHour'
23+
| 'getCellValues'
24+
| 'NULL'
25+
| 'findMessage'
26+
| 'ifThen'
27+
| 'everyDay'
28+
| 'getTableRow'
29+
| 'createTableRow'
30+
| 'sendTransactionalEmail'
31+
| 'onlyContinueIf'
32+
| 'createTileRow'
33+
| 'dateTime'
34+
| 'httpRequest'
35+
| 'everyMonth'
36+
| 'createLetter'
37+
| 'updateSingleRow'
38+
| 'catchRawWebhook'
39+
| 'newSubmission'
40+
| 'findSingleRow'
41+
| 'performCalculation',
42+
appKey:
43+
| null
44+
| 'slack'
45+
| 'm365-excel'
46+
| 'custom-api'
47+
| 'webhook'
48+
| 'delay'
49+
| 'lettersg'
50+
| 'formatter'
51+
| 'tiles'
52+
| 'scheduler'
53+
| 'postman'
54+
| 'formsg'
55+
| 'calculator'
56+
| 'toolbox',
57+
type: 'action' | 'trigger',
58+
flowId: string,
59+
position: number,
60+
parameters?: Record<string, any>,
61+
config?: Record<string, any>,
62+
) {
63+
await Step.query().insert({
64+
key,
65+
appKey,
66+
type,
67+
flowId,
68+
position,
69+
parameters: parameters ?? {},
70+
config: config ?? {},
71+
})
72+
}

packages/backend/src/graphql/mutations/delete-from-s3.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const deleteFromS3: MutationResolvers['deleteFromS3'] = async (
2525
.orderBy('steps.position', 'asc')
2626

2727
// remove attachment from all steps to prevent execution failure
28-
steps.map(
28+
const deletePromises = steps.map(
2929
async (step: { id: string; parameters: { attachments?: string[] } }) => {
3030
const { id: stepId, parameters } = step
3131
const { attachments = [] } = parameters
@@ -43,6 +43,8 @@ const deleteFromS3: MutationResolvers['deleteFromS3'] = async (
4343
}
4444
},
4545
)
46+
await Promise.allSettled(deletePromises)
47+
4648
return await deleteObjects(COMMON_S3_BUCKET, [{ Key: objectKey }])
4749
}
4850

packages/backend/src/helpers/csp.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ const helmetOptions: HelmetOptions = {
1818
// For proxying datadog rum
1919
'https://rum-proxy.plumber.gov.sg',
2020
// For S3 bucket
21-
`https://upload-${appConfig.appEnv}.plumber.gov.sg`,
2221
'https://plumber-uat-attachment-bucket-private-0d9400e.s3.ap-southeast-1.amazonaws.com',
2322
'https://plumber-staging-attachment-bucket-private-ab28487.s3.ap-southeast-1.amazonaws.com',
2423
'https://plumber-prod-attachment-bucket-private-beb3aa3.s3.ap-southeast-1.amazonaws.com',

packages/frontend/src/hooks/useS3Operations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const useS3Operations = (
5656

5757
const deleteFromS3 = async (file: any) => {
5858
try {
59-
const { name: filename, value } = file
59+
const { name: filename, value, displayedValue } = file
6060
const flowId = getValues('flowId')
6161
setIsDeleting(true)
6262
await deleteFile({ variables: { id: value } })
@@ -71,7 +71,7 @@ export const useS3Operations = (
7171

7272
await refetchFlow()
7373

74-
triggerToast(`${filename} deleted successfully`, 'success')
74+
triggerToast(`${displayedValue} deleted successfully`, 'success')
7575
setIsDeleting(false)
7676
options.onSuccess?.(filename)
7777
return true

0 commit comments

Comments
 (0)