File size: 4,606 Bytes
aec3094 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | import { TestCaseExecutionRepository, TestRunRepository } from '@n8n/db';
import type { User } from '@n8n/db';
import { Delete, Get, Post, RestController } from '@n8n/decorators';
import express from 'express';
import { InstanceSettings } from 'n8n-core';
import { UnexpectedError } from 'n8n-workflow';
import { ConflictError } from '@/errors/response-errors/conflict.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { NotImplementedError } from '@/errors/response-errors/not-implemented.error';
import { TestRunnerService } from '@/evaluation.ee/test-runner/test-runner.service.ee';
import { TestRunsRequest } from '@/evaluation.ee/test-runs.types.ee';
import { listQueryMiddleware } from '@/middlewares';
import { getSharedWorkflowIds } from '@/public-api/v1/handlers/workflows/workflows.service';
import { Telemetry } from '@/telemetry';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
@RestController('/workflows')
export class TestRunsController {
constructor(
private readonly testRunRepository: TestRunRepository,
private readonly workflowFinderService: WorkflowFinderService,
private readonly testCaseExecutionRepository: TestCaseExecutionRepository,
private readonly testRunnerService: TestRunnerService,
private readonly instanceSettings: InstanceSettings,
private readonly telemetry: Telemetry,
) {}
/**
* Get the test run (or just check that it exists and the user has access to it)
*/
private async getTestRun(testRunId: string, workflowId: string, user: User) {
const sharedWorkflowsIds = await getSharedWorkflowIds(user, ['workflow:read']);
if (!sharedWorkflowsIds.includes(workflowId)) {
throw new NotFoundError('Test run not found');
}
const testRun = await this.testRunRepository.findOne({
where: { id: testRunId },
});
if (!testRun) throw new NotFoundError('Test run not found');
return testRun;
}
@Get('/:workflowId/test-runs', { middlewares: listQueryMiddleware })
async getMany(req: TestRunsRequest.GetMany) {
const { workflowId } = req.params;
return await this.testRunRepository.getMany(workflowId, req.listQueryOptions);
}
@Get('/:workflowId/test-runs/:id')
async getOne(req: TestRunsRequest.GetOne) {
const { id } = req.params;
try {
await this.getTestRun(req.params.id, req.params.workflowId, req.user); // FIXME: do not fetch test run twice
return await this.testRunRepository.getTestRunSummaryById(id);
} catch (error) {
if (error instanceof UnexpectedError) throw new NotFoundError(error.message);
throw error;
}
}
@Get('/:workflowId/test-runs/:id/test-cases')
async getTestCases(req: TestRunsRequest.GetCases) {
await this.getTestRun(req.params.id, req.params.workflowId, req.user);
return await this.testCaseExecutionRepository.find({
where: { testRun: { id: req.params.id } },
});
}
@Delete('/:workflowId/test-runs/:id')
async delete(req: TestRunsRequest.Delete) {
const { id: testRunId } = req.params;
// Check test run exist
await this.getTestRun(req.params.id, req.params.workflowId, req.user);
await this.testRunRepository.delete({ id: testRunId });
this.telemetry.track('User deleted a run', { run_id: testRunId });
return { success: true };
}
@Post('/:workflowId/test-runs/:id/cancel')
async cancel(req: TestRunsRequest.Cancel, res: express.Response) {
if (this.instanceSettings.isMultiMain) {
throw new NotImplementedError('Cancelling test runs is not yet supported in multi-main mode');
}
const { id: testRunId } = req.params;
// Check test definition and test run exist
const testRun = await this.getTestRun(req.params.id, req.params.workflowId, req.user);
if (this.testRunnerService.canBeCancelled(testRun)) {
const message = `The test run "${testRunId}" cannot be cancelled`;
throw new ConflictError(message);
}
await this.testRunnerService.cancelTestRun(testRunId);
res.status(202).json({ success: true });
}
@Post('/:workflowId/test-runs/new')
async create(req: TestRunsRequest.Create, res: express.Response) {
const { workflowId } = req.params;
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, req.user, [
'workflow:read',
]);
if (!workflow) {
// user trying to access a workflow they do not own
// and was not shared to them
// Or does not exist.
return res.status(404).json({ message: 'Not Found' });
}
// We do not await for the test run to complete
void this.testRunnerService.runTest(req.user, workflow.id);
return res.status(202).json({ success: true });
}
}
|