Skip to content

Commit 2c12295

Browse files
author
Roman Snapko
committed
test: add unit tests for recordTriggerFailure in flowRunService to validate trigger failure handling logic
1 parent 83180df commit 2c12295

2 files changed

Lines changed: 157 additions & 3 deletions

File tree

packages/server/api/src/app/flows/flow-run/flow-run-service.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,9 @@ export const flowRunService = {
148148
}): Promise<void> {
149149
const flowVersion = await flowVersionService.getOneOrThrow(flowVersionId);
150150

151-
const stepName = flowVersion.trigger.name;
152-
153151
const executionState: ExecutionState = {
154152
steps: {
155-
[stepName]: {
153+
[flowVersion.trigger.name]: {
156154
type: flowVersion.trigger.type,
157155
status: StepOutputStatus.FAILED,
158156
input: triggerInput,
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
export const mockedRepo = {
2+
findOneBy: jest.fn(),
3+
createQueryBuilder: jest.fn(),
4+
save: jest.fn(),
5+
update: jest.fn(),
6+
};
7+
8+
jest.mock('../../../src/app/core/db/repo-factory', () => {
9+
return {
10+
repoFactory: () => jest.fn().mockReturnValue(mockedRepo),
11+
};
12+
});
13+
14+
const fileServiceMock = {
15+
save: jest.fn(),
16+
};
17+
jest.mock('../../../src/app/file/file.service', () => {
18+
return {
19+
fileService: fileServiceMock,
20+
};
21+
});
22+
23+
const flowVersionServiceMock = {
24+
getOneOrThrow: jest.fn(),
25+
};
26+
jest.mock('../../../src/app/flows/flow-version/flow-version.service', () => {
27+
return {
28+
flowVersionService: flowVersionServiceMock,
29+
};
30+
});
31+
32+
const logSerializerMock = {
33+
serialize: jest.fn(),
34+
};
35+
jest.mock('../../../src/app/flows/flow-run/log-serializer', () => {
36+
return {
37+
logSerializer: logSerializerMock,
38+
};
39+
});
40+
41+
import {
42+
FileCompression,
43+
FileType,
44+
FlowRunStatus,
45+
FlowRunTriggerSource,
46+
RunEnvironment,
47+
StepOutputStatus,
48+
} from '@openops/shared';
49+
import { flowRunService } from '../../../src/app/flows/flow-run/flow-run-service';
50+
51+
describe('flowRunService.recordTriggerFailure', () => {
52+
const now = new Date('2024-01-02T03:04:05.000Z');
53+
54+
beforeAll(() => {
55+
jest.useFakeTimers();
56+
});
57+
58+
beforeEach(() => {
59+
jest.setSystemTime(now);
60+
jest.clearAllMocks();
61+
logSerializerMock.serialize.mockResolvedValue(
62+
Buffer.from('compressed-logs'),
63+
);
64+
fileServiceMock.save.mockResolvedValue({ id: 'file_123' });
65+
flowVersionServiceMock.getOneOrThrow.mockResolvedValue({
66+
id: 'fv_1',
67+
flowId: 'flow_1',
68+
displayName: 'My Flow V1',
69+
trigger: {
70+
name: 'triggerStep',
71+
type: 'POLLING',
72+
},
73+
});
74+
});
75+
76+
afterAll(() => {
77+
jest.useRealTimers();
78+
});
79+
80+
it('should create a failed flow run with logs when triggerInput is provided', async () => {
81+
await flowRunService.recordTriggerFailure({
82+
projectId: 'proj_1',
83+
flowVersionId: 'fv_1',
84+
errorMessage: 'Trigger failed to execute',
85+
reason: 'TRIGGER_ERROR',
86+
triggerInput: { a: 1 },
87+
});
88+
89+
expect(logSerializerMock.serialize).toHaveBeenCalledTimes(1);
90+
const serializeArg = (logSerializerMock.serialize as jest.Mock).mock
91+
.calls[0][0];
92+
expect(serializeArg).toEqual({
93+
executionState: {
94+
steps: {
95+
triggerStep: {
96+
type: 'POLLING',
97+
status: StepOutputStatus.FAILED,
98+
input: { a: 1 },
99+
errorMessage: 'Trigger failed to execute',
100+
},
101+
},
102+
},
103+
});
104+
105+
expect(fileServiceMock.save).toHaveBeenCalledWith({
106+
data: Buffer.from('compressed-logs'),
107+
type: FileType.FLOW_RUN_LOG,
108+
compression: FileCompression.GZIP,
109+
projectId: 'proj_1',
110+
});
111+
112+
expect(mockedRepo.save).toHaveBeenCalledTimes(1);
113+
const saved = mockedRepo.save.mock.calls[0][0];
114+
expect(saved).toMatchObject({
115+
projectId: 'proj_1',
116+
flowId: 'flow_1',
117+
flowVersionId: 'fv_1',
118+
environment: RunEnvironment.PRODUCTION,
119+
flowDisplayName: 'My Flow V1',
120+
startTime: now.toISOString(),
121+
finishTime: now.toISOString(),
122+
status: FlowRunStatus.FAILED,
123+
triggerSource: FlowRunTriggerSource.TRIGGERED,
124+
terminationReason: 'TRIGGER_ERROR',
125+
tasks: 0,
126+
duration: 0,
127+
tags: [],
128+
logsFileId: 'file_123',
129+
});
130+
131+
expect(typeof saved.id).toBe('string');
132+
expect(saved.id.length).toBeGreaterThan(0);
133+
});
134+
135+
it('should create a failed flow run without triggerInput when not provided', async () => {
136+
await flowRunService.recordTriggerFailure({
137+
projectId: 'proj_2',
138+
flowVersionId: 'fv_1',
139+
errorMessage: 'Boom',
140+
reason: 'TRIGGER_ERROR_NO_INPUT',
141+
});
142+
143+
const serializeArg = (logSerializerMock.serialize as jest.Mock).mock
144+
.calls[0][0];
145+
expect(serializeArg.executionState.steps.triggerStep).toEqual({
146+
type: 'POLLING',
147+
status: StepOutputStatus.FAILED,
148+
input: undefined,
149+
errorMessage: 'Boom',
150+
});
151+
152+
const saved = mockedRepo.save.mock.calls[0][0];
153+
expect(saved.projectId).toBe('proj_2');
154+
expect(saved.terminationReason).toBe('TRIGGER_ERROR_NO_INPUT');
155+
});
156+
});

0 commit comments

Comments
 (0)