2023-10-04 06:32:05 -07:00
|
|
|
import config from '@/config';
|
2024-08-02 06:18:33 -07:00
|
|
|
import { BinaryDataService, InstanceSettings } from 'n8n-core';
|
2023-11-08 07:29:39 -08:00
|
|
|
import type { ExecutionStatus } from 'n8n-workflow';
|
2023-11-10 06:04:26 -08:00
|
|
|
import Container from 'typedi';
|
2023-10-04 06:32:05 -07:00
|
|
|
|
2024-08-28 04:59:27 -07:00
|
|
|
import * as testDb from './shared/test-db';
|
2024-08-27 08:24:20 -07:00
|
|
|
import type { ExecutionEntity } from '@/databases/entities/execution-entity';
|
|
|
|
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
|
|
|
|
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
|
2023-10-25 07:35:22 -07:00
|
|
|
import { TIME } from '@/constants';
|
2023-11-02 04:24:25 -07:00
|
|
|
import { PruningService } from '@/services/pruning.service';
|
2024-08-22 02:10:37 -07:00
|
|
|
import { Logger } from '@/logger';
|
2023-11-10 06:04:26 -08:00
|
|
|
|
|
|
|
import { mockInstance } from '../shared/mocking';
|
2023-11-08 07:29:39 -08:00
|
|
|
import { createWorkflow } from './shared/db/workflows';
|
|
|
|
import { createExecution, createSuccessfulExecution } from './shared/db/executions';
|
2024-06-17 03:49:40 -07:00
|
|
|
import { mock } from 'jest-mock-extended';
|
2023-10-04 06:32:05 -07:00
|
|
|
|
2023-10-24 07:16:45 -07:00
|
|
|
describe('softDeleteOnPruningCycle()', () => {
|
2023-11-02 04:24:25 -07:00
|
|
|
let pruningService: PruningService;
|
2024-08-02 06:18:33 -07:00
|
|
|
const instanceSettings = new InstanceSettings();
|
|
|
|
instanceSettings.markAsLeader();
|
2023-11-02 04:24:25 -07:00
|
|
|
|
2023-10-04 06:32:05 -07:00
|
|
|
const now = new Date();
|
|
|
|
const yesterday = new Date(Date.now() - TIME.DAY);
|
2023-11-08 07:29:39 -08:00
|
|
|
let workflow: WorkflowEntity;
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
await testDb.init();
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
pruningService = new PruningService(
|
|
|
|
mockInstance(Logger),
|
2024-08-02 06:18:33 -07:00
|
|
|
instanceSettings,
|
2023-11-10 06:04:26 -08:00
|
|
|
Container.get(ExecutionRepository),
|
2023-11-02 04:24:25 -07:00
|
|
|
mockInstance(BinaryDataService),
|
2024-08-02 06:18:33 -07:00
|
|
|
mock(),
|
2023-11-02 04:24:25 -07:00
|
|
|
);
|
2023-10-04 06:32:05 -07:00
|
|
|
|
2023-11-08 07:29:39 -08:00
|
|
|
workflow = await createWorkflow();
|
2023-10-04 06:32:05 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
await testDb.truncate(['Execution']);
|
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
await testDb.terminate();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
config.load(config.default);
|
|
|
|
});
|
|
|
|
|
|
|
|
async function findAllExecutions() {
|
2024-01-17 07:08:50 -08:00
|
|
|
return await Container.get(ExecutionRepository).find({
|
2023-10-04 06:32:05 -07:00
|
|
|
order: { id: 'asc' },
|
|
|
|
withDeleted: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
describe('when EXECUTIONS_DATA_PRUNE_MAX_COUNT is set', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
config.set('executions.pruneDataMaxCount', 1);
|
|
|
|
config.set('executions.pruneDataMaxAge', 336);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should mark as deleted based on EXECUTIONS_DATA_PRUNE_MAX_COUNT', async () => {
|
|
|
|
const executions = [
|
2023-11-08 07:29:39 -08:00
|
|
|
await createSuccessfulExecution(workflow),
|
|
|
|
await createSuccessfulExecution(workflow),
|
|
|
|
await createSuccessfulExecution(workflow),
|
2023-10-04 06:32:05 -07:00
|
|
|
];
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: executions[0].id, deletedAt: expect.any(Date) }),
|
|
|
|
expect.objectContaining({ id: executions[1].id, deletedAt: expect.any(Date) }),
|
|
|
|
expect.objectContaining({ id: executions[2].id, deletedAt: null }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should not re-mark already marked executions', async () => {
|
|
|
|
const executions = [
|
2023-11-08 07:29:39 -08:00
|
|
|
await createExecution(
|
2023-10-04 06:32:05 -07:00
|
|
|
{ status: 'success', finished: true, startedAt: now, stoppedAt: now, deletedAt: now },
|
|
|
|
workflow,
|
|
|
|
),
|
2023-11-08 07:29:39 -08:00
|
|
|
await createSuccessfulExecution(workflow),
|
2023-10-04 06:32:05 -07:00
|
|
|
];
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: executions[0].id, deletedAt: now }),
|
|
|
|
expect.objectContaining({ id: executions[1].id, deletedAt: null }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test.each<[ExecutionStatus, Partial<ExecutionEntity>]>([
|
|
|
|
['unknown', { startedAt: now, stoppedAt: now }],
|
|
|
|
['canceled', { startedAt: now, stoppedAt: now }],
|
|
|
|
['crashed', { startedAt: now, stoppedAt: now }],
|
2024-03-25 09:52:07 -07:00
|
|
|
['error', { startedAt: now, stoppedAt: now }],
|
2023-10-04 06:32:05 -07:00
|
|
|
['success', { finished: true, startedAt: now, stoppedAt: now }],
|
|
|
|
])('should prune %s executions', async (status, attributes) => {
|
|
|
|
const executions = [
|
2023-11-08 07:29:39 -08:00
|
|
|
await createExecution({ status, ...attributes }, workflow),
|
|
|
|
await createSuccessfulExecution(workflow),
|
2023-10-04 06:32:05 -07:00
|
|
|
];
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: executions[0].id, deletedAt: expect.any(Date) }),
|
|
|
|
expect.objectContaining({ id: executions[1].id, deletedAt: null }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test.each<[ExecutionStatus, Partial<ExecutionEntity>]>([
|
|
|
|
['new', {}],
|
|
|
|
['running', { startedAt: now }],
|
|
|
|
['waiting', { startedAt: now, stoppedAt: now, waitTill: now }],
|
|
|
|
])('should not prune %s executions', async (status, attributes) => {
|
|
|
|
const executions = [
|
2023-11-08 07:29:39 -08:00
|
|
|
await createExecution({ status, ...attributes }, workflow),
|
|
|
|
await createSuccessfulExecution(workflow),
|
2023-10-04 06:32:05 -07:00
|
|
|
];
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: executions[0].id, deletedAt: null }),
|
|
|
|
expect.objectContaining({ id: executions[1].id, deletedAt: null }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when EXECUTIONS_DATA_MAX_AGE is set', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
config.set('executions.pruneDataMaxAge', 1); // 1h
|
|
|
|
config.set('executions.pruneDataMaxCount', 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should mark as deleted based on EXECUTIONS_DATA_MAX_AGE', async () => {
|
|
|
|
const executions = [
|
2023-11-08 07:29:39 -08:00
|
|
|
await createExecution(
|
2023-10-04 06:32:05 -07:00
|
|
|
{ finished: true, startedAt: yesterday, stoppedAt: yesterday, status: 'success' },
|
|
|
|
workflow,
|
|
|
|
),
|
2023-11-08 07:29:39 -08:00
|
|
|
await createExecution(
|
2023-10-04 06:32:05 -07:00
|
|
|
{ finished: true, startedAt: now, stoppedAt: now, status: 'success' },
|
|
|
|
workflow,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: executions[0].id, deletedAt: expect.any(Date) }),
|
|
|
|
expect.objectContaining({ id: executions[1].id, deletedAt: null }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should not re-mark already marked executions', async () => {
|
|
|
|
const executions = [
|
2023-11-08 07:29:39 -08:00
|
|
|
await createExecution(
|
2023-10-04 06:32:05 -07:00
|
|
|
{
|
|
|
|
status: 'success',
|
|
|
|
finished: true,
|
|
|
|
startedAt: yesterday,
|
|
|
|
stoppedAt: yesterday,
|
|
|
|
deletedAt: yesterday,
|
|
|
|
},
|
|
|
|
workflow,
|
|
|
|
),
|
2023-11-08 07:29:39 -08:00
|
|
|
await createSuccessfulExecution(workflow),
|
2023-10-04 06:32:05 -07:00
|
|
|
];
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: executions[0].id, deletedAt: yesterday }),
|
|
|
|
expect.objectContaining({ id: executions[1].id, deletedAt: null }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test.each<[ExecutionStatus, Partial<ExecutionEntity>]>([
|
|
|
|
['unknown', { startedAt: yesterday, stoppedAt: yesterday }],
|
|
|
|
['canceled', { startedAt: yesterday, stoppedAt: yesterday }],
|
|
|
|
['crashed', { startedAt: yesterday, stoppedAt: yesterday }],
|
2024-03-25 09:52:07 -07:00
|
|
|
['error', { startedAt: yesterday, stoppedAt: yesterday }],
|
2023-10-04 06:32:05 -07:00
|
|
|
['success', { finished: true, startedAt: yesterday, stoppedAt: yesterday }],
|
|
|
|
])('should prune %s executions', async (status, attributes) => {
|
2023-11-08 07:29:39 -08:00
|
|
|
const execution = await createExecution({ status, ...attributes }, workflow);
|
2023-10-04 06:32:05 -07:00
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: execution.id, deletedAt: expect.any(Date) }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test.each<[ExecutionStatus, Partial<ExecutionEntity>]>([
|
|
|
|
['new', {}],
|
|
|
|
['running', { startedAt: yesterday }],
|
|
|
|
['waiting', { startedAt: yesterday, stoppedAt: yesterday, waitTill: yesterday }],
|
|
|
|
])('should not prune %s executions', async (status, attributes) => {
|
|
|
|
const executions = [
|
2023-11-08 07:29:39 -08:00
|
|
|
await createExecution({ status, ...attributes }, workflow),
|
|
|
|
await createSuccessfulExecution(workflow),
|
2023-10-04 06:32:05 -07:00
|
|
|
];
|
|
|
|
|
2023-11-02 04:24:25 -07:00
|
|
|
await pruningService.softDeleteOnPruningCycle();
|
2023-10-04 06:32:05 -07:00
|
|
|
|
|
|
|
const result = await findAllExecutions();
|
|
|
|
expect(result).toEqual([
|
|
|
|
expect.objectContaining({ id: executions[0].id, deletedAt: null }),
|
|
|
|
expect.objectContaining({ id: executions[1].id, deletedAt: null }),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|