Skip to content

Commit 4e4db8c

Browse files
committed
Merge branch 'master' into feat/async-and-improved-initialization
2 parents 4ce9d2b + 11af13c commit 4e4db8c

File tree

3 files changed

+242
-2
lines changed

3 files changed

+242
-2
lines changed

.github/coding-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ When writing tests, follow these guidelines to ensure consistency and maintainab
99
- **Make tests resilient to refactoring**: Tests should pass even if internal implementation changes, as long as the behavior remains the same.
1010
- Never make up new functions just to make tests pass. Always build tests based on the functions that already exist. If a function needs to be updated/revised/refactored, that is also OK.
1111
- Do not just add a 'markTestSkipped' on tests that look difficult to write. Instead, explain the problem and ask for some additional context before trying again.
12+
- Make sure new tests added into the "integration-tests" directory are actually integration tests.
1213

1314
General guidlines:
1415
- Never edit files that are git ignored.
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import { prismaMock } from '../../prisma-local/mockedClient'
2+
import prisma from '../../prisma-local/clientInstance'
3+
import axios from 'axios'
4+
5+
import { parseTriggerPostData } from '../../utils/validators'
6+
7+
jest.mock('axios')
8+
const mockedAxios = axios as jest.Mocked<typeof axios>
9+
10+
jest.mock('../../utils/validators', () => {
11+
const originalModule = jest.requireActual('../../utils/validators')
12+
return {
13+
...originalModule,
14+
parseTriggerPostData: jest.fn()
15+
}
16+
})
17+
const mockedParseTriggerPostData = parseTriggerPostData as jest.MockedFunction<typeof parseTriggerPostData>
18+
19+
describe('Trigger JSON Validation Unit Tests', () => {
20+
describe('Trigger Creation with JSON Validation', () => {
21+
it('should reject trigger creation with invalid JSON during validation', async () => {
22+
const invalidPostData = '{"amount": <amount>, "currency": <currency>'
23+
24+
mockedParseTriggerPostData.mockImplementation(() => {
25+
throw new SyntaxError('Unexpected end of JSON input')
26+
})
27+
28+
expect(() => {
29+
parseTriggerPostData({
30+
userId: 'test-user',
31+
postData: invalidPostData,
32+
postDataParameters: {} as any
33+
})
34+
}).toThrow('Unexpected end of JSON input')
35+
36+
expect(mockedParseTriggerPostData).toHaveBeenCalled()
37+
})
38+
39+
it('should allow trigger creation with valid JSON', async () => {
40+
const validPostData = '{"amount": <amount>, "currency": <currency>}'
41+
const expectedParsedData = { amount: 100, currency: 'XEC' }
42+
43+
mockedParseTriggerPostData.mockReturnValue(expectedParsedData)
44+
45+
const result = parseTriggerPostData({
46+
userId: 'test-user',
47+
postData: validPostData,
48+
postDataParameters: {} as any
49+
})
50+
51+
expect(result).toEqual(expectedParsedData)
52+
expect(mockedParseTriggerPostData).toHaveBeenCalled()
53+
})
54+
})
55+
56+
describe('Trigger Execution Scenarios', () => {
57+
beforeEach(() => {
58+
jest.clearAllMocks()
59+
60+
prismaMock.triggerLog.create.mockResolvedValue({
61+
id: 1,
62+
triggerId: 'test-trigger',
63+
isError: false,
64+
actionType: 'PostData',
65+
data: '{}',
66+
createdAt: new Date(),
67+
updatedAt: new Date()
68+
})
69+
prisma.triggerLog.create = prismaMock.triggerLog.create
70+
})
71+
72+
it('should demonstrate JSON validation flow differences', async () => {
73+
console.log('=== Test Case 1: Valid JSON ===')
74+
75+
mockedParseTriggerPostData.mockReturnValue({ amount: 100, currency: 'XEC' })
76+
mockedAxios.post.mockResolvedValue({ data: { success: true } })
77+
78+
try {
79+
const result = parseTriggerPostData({
80+
userId: 'user-123',
81+
postData: '{"amount": <amount>, "currency": <currency>}',
82+
postDataParameters: {} as any
83+
})
84+
console.log('✅ JSON parsing succeeded:', result)
85+
console.log('✅ Network request would be made')
86+
} catch (error) {
87+
console.log('❌ Unexpected error:', error)
88+
}
89+
90+
console.log('\n=== Test Case 2: Invalid JSON ===')
91+
92+
mockedParseTriggerPostData.mockImplementation(() => {
93+
throw new SyntaxError('Unexpected end of JSON input')
94+
})
95+
96+
try {
97+
parseTriggerPostData({
98+
userId: 'user-123',
99+
postData: '{"amount": <amount>, "currency": <currency>',
100+
postDataParameters: {} as any
101+
})
102+
console.log('❌ Should not reach here')
103+
} catch (error) {
104+
console.log('✅ JSON parsing failed as expected:', (error as Error).message)
105+
console.log('✅ Network request would NOT be made')
106+
}
107+
108+
expect(mockedParseTriggerPostData).toHaveBeenCalledTimes(2)
109+
})
110+
111+
it('should log JSON validation errors with proper details', async () => {
112+
const testCases = [
113+
{
114+
name: 'Missing closing brace',
115+
postData: '{"amount": <amount>, "currency": <currency>',
116+
expectedError: 'Unexpected end of JSON input'
117+
},
118+
{
119+
name: 'Invalid property syntax',
120+
postData: '{amount: <amount>, "currency": <currency>}',
121+
expectedError: 'Expected property name'
122+
},
123+
{
124+
name: 'Extra comma',
125+
postData: '{"amount": <amount>,, "currency": <currency>}',
126+
expectedError: 'Unexpected token'
127+
}
128+
]
129+
130+
testCases.forEach(({ name, postData, expectedError }) => {
131+
console.log(`\n=== Testing: ${name} ===`)
132+
133+
mockedParseTriggerPostData.mockImplementation(() => {
134+
const error = new SyntaxError(expectedError)
135+
error.name = 'SyntaxError'
136+
throw error
137+
})
138+
139+
try {
140+
parseTriggerPostData({
141+
userId: 'user-123',
142+
postData,
143+
postDataParameters: {} as any
144+
})
145+
console.log('❌ Should have failed')
146+
} catch (error) {
147+
const err = error as Error
148+
console.log('✅ Failed as expected:', err.message)
149+
expect(err.name).toBe('SyntaxError')
150+
expect(err.message).toContain(expectedError)
151+
}
152+
})
153+
})
154+
155+
it('should handle edge cases gracefully', async () => {
156+
const edgeCases = [
157+
{
158+
name: 'Empty post data',
159+
postData: '',
160+
mockError: new Error('No data to parse')
161+
},
162+
{
163+
name: 'Null-like post data',
164+
postData: 'null',
165+
mockError: new Error('Invalid null data')
166+
},
167+
{
168+
name: 'Non-object JSON',
169+
postData: '"just a string"',
170+
mockError: new Error('Expected object')
171+
}
172+
]
173+
174+
edgeCases.forEach(({ name, postData, mockError }) => {
175+
console.log(`\n=== Testing edge case: ${name} ===`)
176+
177+
mockedParseTriggerPostData.mockImplementation(() => {
178+
throw mockError
179+
})
180+
181+
try {
182+
parseTriggerPostData({
183+
userId: 'user-123',
184+
postData,
185+
postDataParameters: {} as any
186+
})
187+
console.log('❌ Should have failed')
188+
} catch (error) {
189+
console.log('✅ Handled gracefully:', (error as Error).message)
190+
}
191+
})
192+
})
193+
})
194+
195+
describe('Performance and Efficiency Benefits', () => {
196+
it('should demonstrate network request avoidance', async () => {
197+
let networkRequestCount = 0
198+
199+
mockedAxios.post.mockImplementation(async () => {
200+
networkRequestCount++
201+
return { data: { success: true } }
202+
})
203+
204+
console.log('\n=== Performance Test: Valid vs Invalid JSON ===')
205+
206+
mockedParseTriggerPostData.mockReturnValue({ amount: 100 })
207+
208+
try {
209+
parseTriggerPostData({
210+
userId: 'user-123',
211+
postData: '{"amount": <amount>}',
212+
postDataParameters: {} as any
213+
})
214+
await mockedAxios.post('https://example.com', { amount: 100 })
215+
console.log('✅ Valid JSON: Network request made')
216+
} catch (error) {
217+
console.log('❌ Unexpected error with valid JSON')
218+
}
219+
220+
mockedParseTriggerPostData.mockImplementation(() => {
221+
throw new SyntaxError('Invalid JSON')
222+
})
223+
224+
try {
225+
parseTriggerPostData({
226+
userId: 'user-123',
227+
postData: '{"amount": <amount>',
228+
postDataParameters: {} as any
229+
})
230+
} catch (error) {
231+
console.log('✅ Invalid JSON: No network request made')
232+
}
233+
234+
console.log(`Network requests made: ${networkRequestCount}`)
235+
expect(networkRequestCount).toBe(1)
236+
})
237+
})
238+
})

utils/validators.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ interface TriggerSignature {
231231

232232
function getSignaturePayload (postData: string, postDataParameters: PostDataParameters): string {
233233
const includedVariables = TRIGGER_POST_VARIABLES.filter(v => postData.includes(v)).sort()
234-
return includedVariables.map(varString => {
234+
const result = includedVariables.map(varString => {
235235
const key = varString.replace('<', '').replace('>', '') as keyof PostDataParameters
236236
let valueString = ''
237237
if (key === 'opReturn') {
@@ -241,7 +241,8 @@ function getSignaturePayload (postData: string, postDataParameters: PostDataPara
241241
valueString = postDataParameters[key] as string
242242
}
243243
return valueString
244-
}).join('+')
244+
}).filter(value => value !== undefined && value !== null && value !== '')
245+
return result.join('+')
245246
}
246247

247248
export function signPostData ({ userId, postData, postDataParameters }: PaybuttonTriggerParseParameters): TriggerSignature {

0 commit comments

Comments
 (0)