feat(core): Update data model for Evaluations (no-changelog) (#15520)

Co-authored-by: Yiorgis Gozadinos <yiorgis@n8n.io>
Co-authored-by: JP van Oosten <jp@n8n.io>
This commit is contained in:
Eugene
2025-05-22 12:55:31 +02:00
committed by GitHub
parent 07d526e9d6
commit 8152f8c6a7
42 changed files with 512 additions and 3327 deletions

View File

@@ -1,7 +1,5 @@
import type { User } from '@n8n/db';
import type { TestDefinition } from '@n8n/db';
import { ProjectRepository } from '@n8n/db';
import { TestDefinitionRepository } from '@n8n/db';
import { TestRunRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import { mockInstance } from 'n8n-core/test/utils';
@@ -17,8 +15,6 @@ import * as utils from '@test-integration/utils';
let authOwnerAgent: SuperAgentTest;
let workflowUnderTest: IWorkflowBase;
let otherWorkflow: IWorkflowBase;
let testDefinition: TestDefinition;
let otherTestDefinition: TestDefinition;
let ownerShell: User;
const testRunner = mockInstance(TestRunnerService);
@@ -34,83 +30,68 @@ beforeAll(async () => {
});
beforeEach(async () => {
await testDb.truncate(['TestDefinition', 'TestRun', 'WorkflowEntity', 'SharedWorkflow']);
await testDb.truncate(['TestRun', 'WorkflowEntity', 'SharedWorkflow']);
workflowUnderTest = await createWorkflow({ name: 'workflow-under-test' }, ownerShell);
testDefinition = Container.get(TestDefinitionRepository).create({
name: 'test',
workflow: { id: workflowUnderTest.id },
});
await Container.get(TestDefinitionRepository).save(testDefinition);
otherWorkflow = await createWorkflow({ name: 'other-workflow' });
otherTestDefinition = Container.get(TestDefinitionRepository).create({
name: 'other-test',
workflow: { id: otherWorkflow.id },
});
await Container.get(TestDefinitionRepository).save(otherTestDefinition);
});
describe('GET /evaluation/test-definitions/:testDefinitionId/runs', () => {
test('should retrieve empty list of runs for a test definition', async () => {
const resp = await authOwnerAgent.get(`/evaluation/test-definitions/${testDefinition.id}/runs`);
describe('GET /workflows/:workflowId/test-runs', () => {
test('should retrieve empty list of test runs', async () => {
const resp = await authOwnerAgent.get(`/workflows/${workflowUnderTest.id}/test-runs`);
expect(resp.statusCode).toBe(200);
expect(resp.body.data).toEqual([]);
});
test('should return 404 if test definition does not exist', async () => {
const resp = await authOwnerAgent.get('/evaluation/test-definitions/123/runs');
// TODO: replace with non existent workflow
// test('should return 404 if test definition does not exist', async () => {
// const resp = await authOwnerAgent.get('/evaluation/test-definitions/123/runs');
//
// expect(resp.statusCode).toBe(404);
// });
expect(resp.statusCode).toBe(404);
});
// TODO: replace with workflow that is not accessible to the user
// test('should return 404 if user does not have access to test definition', async () => {
// const resp = await authOwnerAgent.get(
// `/evaluation/test-definitions/${otherTestDefinition.id}/runs`,
// );
//
// expect(resp.statusCode).toBe(404);
// });
test('should return 404 if user does not have access to test definition', async () => {
const resp = await authOwnerAgent.get(
`/evaluation/test-definitions/${otherTestDefinition.id}/runs`,
);
expect(resp.statusCode).toBe(404);
});
test('should retrieve list of runs for a test definition', async () => {
test('should retrieve list of runs for a workflow', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(testDefinition.id);
const testRun = await testRunRepository.createTestRun(workflowUnderTest.id);
const resp = await authOwnerAgent.get(`/evaluation/test-definitions/${testDefinition.id}/runs`);
const resp = await authOwnerAgent.get(`/workflows/${workflowUnderTest.id}/test-runs`);
expect(resp.statusCode).toBe(200);
expect(resp.body.data).toEqual([
expect.objectContaining({
id: testRun.id,
status: 'new',
testDefinitionId: testDefinition.id,
runAt: null,
completedAt: null,
}),
]);
});
test('should retrieve list of runs for a test definition with pagination', async () => {
test('should retrieve list of test runs for a workflow with pagination', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun1 = await testRunRepository.createTestRun(testDefinition.id);
const testRun1 = await testRunRepository.createTestRun(workflowUnderTest.id);
// Mark as running just to make a slight delay between the runs
await testRunRepository.markAsRunning(testRun1.id, 10);
const testRun2 = await testRunRepository.createTestRun(testDefinition.id);
await testRunRepository.markAsRunning(testRun1.id);
const testRun2 = await testRunRepository.createTestRun(workflowUnderTest.id);
// Fetch the first page
const resp = await authOwnerAgent.get(
`/evaluation/test-definitions/${testDefinition.id}/runs?take=1`,
);
const resp = await authOwnerAgent.get(`/workflows/${workflowUnderTest.id}/test-runs?take=1`);
expect(resp.statusCode).toBe(200);
expect(resp.body.data).toEqual([
expect.objectContaining({
id: testRun2.id,
status: 'new',
testDefinitionId: testDefinition.id,
runAt: null,
completedAt: null,
}),
@@ -118,7 +99,7 @@ describe('GET /evaluation/test-definitions/:testDefinitionId/runs', () => {
// Fetch the second page
const resp2 = await authOwnerAgent.get(
`/evaluation/test-definitions/${testDefinition.id}/runs?take=1&skip=1`,
`/workflows/${workflowUnderTest.id}/test-runs?take=1&skip=1`,
);
expect(resp2.statusCode).toBe(200);
@@ -126,7 +107,6 @@ describe('GET /evaluation/test-definitions/:testDefinitionId/runs', () => {
expect.objectContaining({
id: testRun1.id,
status: 'running',
testDefinitionId: testDefinition.id,
runAt: expect.any(String),
completedAt: null,
}),
@@ -134,13 +114,13 @@ describe('GET /evaluation/test-definitions/:testDefinitionId/runs', () => {
});
});
describe('GET /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
test('should retrieve test run for a test definition', async () => {
describe('GET /workflows/:workflowId/test-runs/:id', () => {
test('should retrieve specific test run for a workflow', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(testDefinition.id);
const testRun = await testRunRepository.createTestRun(workflowUnderTest.id);
const resp = await authOwnerAgent.get(
`/evaluation/test-definitions/${testDefinition.id}/runs/${testRun.id}`,
`/workflows/${workflowUnderTest.id}/test-runs/${testRun.id}`,
);
expect(resp.statusCode).toBe(200);
@@ -148,7 +128,6 @@ describe('GET /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
expect.objectContaining({
id: testRun.id,
status: 'new',
testDefinitionId: testDefinition.id,
runAt: null,
completedAt: null,
}),
@@ -156,25 +135,21 @@ describe('GET /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
});
test('should return 404 if test run does not exist', async () => {
const resp = await authOwnerAgent.get(
`/evaluation/test-definitions/${testDefinition.id}/runs/123`,
);
const resp = await authOwnerAgent.get(`/workflows/${workflowUnderTest.id}/test-runs/123`);
expect(resp.statusCode).toBe(404);
});
test('should return 404 if user does not have access to test definition', async () => {
test('should return 404 if user does not have access to the workflow', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(otherTestDefinition.id);
const testRun = await testRunRepository.createTestRun(otherWorkflow.id);
const resp = await authOwnerAgent.get(
`/evaluation/test-definitions/${otherTestDefinition.id}/runs/${testRun.id}`,
);
const resp = await authOwnerAgent.get(`/workflows/${otherWorkflow.id}/test-runs/${testRun.id}`);
expect(resp.statusCode).toBe(404);
});
test('should retrieve test run for a test definition of a shared workflow', async () => {
test('should retrieve test run of a shared workflow', async () => {
const memberShell = await createUserShell('global:member');
const memberAgent = testServer.authAgentFor(memberShell);
const memberPersonalProject = await Container.get(
@@ -190,11 +165,11 @@ describe('GET /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
// Create a test run for the shared workflow
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(testDefinition.id);
const testRun = await testRunRepository.createTestRun(workflowUnderTest.id);
// Check if member can retrieve the test run of a shared workflow
const resp = await memberAgent.get(
`/evaluation/test-definitions/${testDefinition.id}/runs/${testRun.id}`,
`/workflows/${workflowUnderTest.id}/test-runs/${testRun.id}`,
);
expect(resp.statusCode).toBe(200);
@@ -209,10 +184,10 @@ describe('GET /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
describe('DELETE /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
test('should delete test run for a test definition', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(testDefinition.id);
const testRun = await testRunRepository.createTestRun(workflowUnderTest.id);
const resp = await authOwnerAgent.delete(
`/evaluation/test-definitions/${testDefinition.id}/runs/${testRun.id}`,
`/workflows/${workflowUnderTest.id}/test-runs/${testRun.id}`,
);
expect(resp.statusCode).toBe(200);
@@ -223,19 +198,17 @@ describe('DELETE /evaluation/test-definitions/:testDefinitionId/runs/:id', () =>
});
test('should return 404 if test run does not exist', async () => {
const resp = await authOwnerAgent.delete(
`/evaluation/test-definitions/${testDefinition.id}/runs/123`,
);
const resp = await authOwnerAgent.delete(`/workflows/${workflowUnderTest.id}/test-runs/123`);
expect(resp.statusCode).toBe(404);
});
test('should return 404 if user does not have access to test definition', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(otherTestDefinition.id);
const testRun = await testRunRepository.createTestRun(otherWorkflow.id);
const resp = await authOwnerAgent.delete(
`/evaluation/test-definitions/${otherTestDefinition.id}/runs/${testRun.id}`,
`/workflows/${otherWorkflow.id}/test-runs/${testRun.id}`,
);
expect(resp.statusCode).toBe(404);
@@ -245,12 +218,12 @@ describe('DELETE /evaluation/test-definitions/:testDefinitionId/runs/:id', () =>
describe('POST /evaluation/test-definitions/:testDefinitionId/runs/:id/cancel', () => {
test('should cancel test run', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(testDefinition.id);
const testRun = await testRunRepository.createTestRun(workflowUnderTest.id);
jest.spyOn(testRunRepository, 'markAsCancelled');
const resp = await authOwnerAgent.post(
`/evaluation/test-definitions/${testDefinition.id}/runs/${testRun.id}/cancel`,
`/workflows/${workflowUnderTest.id}/test-runs/${testRun.id}/cancel`,
);
expect(resp.statusCode).toBe(202);
@@ -261,24 +234,24 @@ describe('POST /evaluation/test-definitions/:testDefinitionId/runs/:id/cancel',
test('should return 404 if test run does not exist', async () => {
const resp = await authOwnerAgent.post(
`/evaluation/test-definitions/${testDefinition.id}/runs/123/cancel`,
`/workflows/${workflowUnderTest.id}/test-runs/123/cancel`,
);
expect(resp.statusCode).toBe(404);
});
test('should return 404 if test definition does not exist', async () => {
const resp = await authOwnerAgent.post('/evaluation/test-definitions/123/runs/123/cancel');
test('should return 404 if workflow does not exist', async () => {
const resp = await authOwnerAgent.post('/workflows/123/test-runs/123/cancel');
expect(resp.statusCode).toBe(404);
});
test('should return 404 if user does not have access to test definition', async () => {
test('should return 404 if user does not have access to the workflow', async () => {
const testRunRepository = Container.get(TestRunRepository);
const testRun = await testRunRepository.createTestRun(otherTestDefinition.id);
const testRun = await testRunRepository.createTestRun(otherWorkflow.id);
const resp = await authOwnerAgent.post(
`/evaluation/test-definitions/${otherTestDefinition.id}/runs/${testRun.id}/cancel`,
`/workflows/${otherWorkflow.id}/test-runs/${testRun.id}/cancel`,
);
expect(resp.statusCode).toBe(404);