mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-30 05:42:00 -08:00
feat(core): Add workflowId filter to the test-definitions endpoint (no-changelog) (#11831)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run
This commit is contained in:
parent
3341267518
commit
6b23ad0c12
|
@ -3,6 +3,7 @@ import { DataSource, In, Repository } from '@n8n/typeorm';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
import { TestDefinition } from '@/databases/entities/test-definition.ee';
|
import { TestDefinition } from '@/databases/entities/test-definition.ee';
|
||||||
|
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
|
||||||
import type { ListQuery } from '@/requests';
|
import type { ListQuery } from '@/requests';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
|
@ -14,12 +15,21 @@ export class TestDefinitionRepository extends Repository<TestDefinition> {
|
||||||
async getMany(accessibleWorkflowIds: string[], options?: ListQuery.Options) {
|
async getMany(accessibleWorkflowIds: string[], options?: ListQuery.Options) {
|
||||||
if (accessibleWorkflowIds.length === 0) return { tests: [], count: 0 };
|
if (accessibleWorkflowIds.length === 0) return { tests: [], count: 0 };
|
||||||
|
|
||||||
const where: FindOptionsWhere<TestDefinition> = {
|
const where: FindOptionsWhere<TestDefinition> = {};
|
||||||
...options?.filter,
|
|
||||||
workflow: {
|
if (options?.filter?.workflowId) {
|
||||||
|
if (!accessibleWorkflowIds.includes(options.filter.workflowId as string)) {
|
||||||
|
throw new ForbiddenError('User does not have access to the workflow');
|
||||||
|
}
|
||||||
|
|
||||||
|
where.workflow = {
|
||||||
|
id: options.filter.workflowId as string,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
where.workflow = {
|
||||||
id: In(accessibleWorkflowIds),
|
id: In(accessibleWorkflowIds),
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const findManyOptions: FindManyOptions<TestDefinition> = {
|
const findManyOptions: FindManyOptions<TestDefinition> = {
|
||||||
where,
|
where,
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Expose } from 'class-transformer';
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
import { BaseFilter } from './base.filter.dto';
|
||||||
|
|
||||||
|
export class TestDefinitionsFilter extends BaseFilter {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
@Expose()
|
||||||
|
workflowId?: string;
|
||||||
|
|
||||||
|
static async fromString(rawFilter: string) {
|
||||||
|
return await this.toFilter(rawFilter, TestDefinitionsFilter);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import * as ResponseHelper from '@/response-helper';
|
||||||
import { toError } from '@/utils';
|
import { toError } from '@/utils';
|
||||||
|
|
||||||
import { CredentialsFilter } from './dtos/credentials.filter.dto';
|
import { CredentialsFilter } from './dtos/credentials.filter.dto';
|
||||||
|
import { TestDefinitionsFilter } from './dtos/test-definitions.filter.dto';
|
||||||
import { UserFilter } from './dtos/user.filter.dto';
|
import { UserFilter } from './dtos/user.filter.dto';
|
||||||
import { WorkflowFilter } from './dtos/workflow.filter.dto';
|
import { WorkflowFilter } from './dtos/workflow.filter.dto';
|
||||||
|
|
||||||
|
@ -25,6 +26,8 @@ export const filterListQueryMiddleware = async (
|
||||||
Filter = CredentialsFilter;
|
Filter = CredentialsFilter;
|
||||||
} else if (req.baseUrl.endsWith('users')) {
|
} else if (req.baseUrl.endsWith('users')) {
|
||||||
Filter = UserFilter;
|
Filter = UserFilter;
|
||||||
|
} else if (req.baseUrl.endsWith('test-definitions')) {
|
||||||
|
Filter = TestDefinitionsFilter;
|
||||||
} else {
|
} else {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import * as utils from './../shared/utils/';
|
||||||
|
|
||||||
let authOwnerAgent: SuperAgentTest;
|
let authOwnerAgent: SuperAgentTest;
|
||||||
let workflowUnderTest: WorkflowEntity;
|
let workflowUnderTest: WorkflowEntity;
|
||||||
|
let workflowUnderTest2: WorkflowEntity;
|
||||||
let evaluationWorkflow: WorkflowEntity;
|
let evaluationWorkflow: WorkflowEntity;
|
||||||
let otherWorkflow: WorkflowEntity;
|
let otherWorkflow: WorkflowEntity;
|
||||||
let ownerShell: User;
|
let ownerShell: User;
|
||||||
|
@ -29,6 +30,7 @@ beforeEach(async () => {
|
||||||
await testDb.truncate(['TestDefinition', 'Workflow', 'AnnotationTag']);
|
await testDb.truncate(['TestDefinition', 'Workflow', 'AnnotationTag']);
|
||||||
|
|
||||||
workflowUnderTest = await createWorkflow({ name: 'workflow-under-test' }, ownerShell);
|
workflowUnderTest = await createWorkflow({ name: 'workflow-under-test' }, ownerShell);
|
||||||
|
workflowUnderTest2 = await createWorkflow({ name: 'workflow-under-test-2' }, ownerShell);
|
||||||
evaluationWorkflow = await createWorkflow({ name: 'evaluation-workflow' }, ownerShell);
|
evaluationWorkflow = await createWorkflow({ name: 'evaluation-workflow' }, ownerShell);
|
||||||
otherWorkflow = await createWorkflow({ name: 'other-workflow' });
|
otherWorkflow = await createWorkflow({ name: 'other-workflow' });
|
||||||
annotationTag = (await createAnnotationTags(['test-tag']))[0];
|
annotationTag = (await createAnnotationTags(['test-tag']))[0];
|
||||||
|
@ -90,6 +92,44 @@ describe('GET /evaluation/test-definitions', () => {
|
||||||
expect(resp.body.data.count).toBe(15);
|
expect(resp.body.data.count).toBe(15);
|
||||||
expect(resp.body.data.testDefinitions).toHaveLength(5);
|
expect(resp.body.data.testDefinitions).toHaveLength(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should retrieve test definitions list for a workflow', async () => {
|
||||||
|
// Add a bunch of test definitions for two different workflows
|
||||||
|
const testDefinitions = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 15; i++) {
|
||||||
|
const newTest = Container.get(TestDefinitionRepository).create({
|
||||||
|
name: `test-${i}`,
|
||||||
|
workflow: { id: workflowUnderTest.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const newTest2 = Container.get(TestDefinitionRepository).create({
|
||||||
|
name: `test-${i * 2}`,
|
||||||
|
workflow: { id: workflowUnderTest2.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
testDefinitions.push(newTest, newTest2);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Container.get(TestDefinitionRepository).save(testDefinitions);
|
||||||
|
|
||||||
|
// Fetch test definitions of a second workflow
|
||||||
|
let resp = await authOwnerAgent.get(
|
||||||
|
`/evaluation/test-definitions?filter=${JSON.stringify({ workflowId: workflowUnderTest2.id })}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resp.statusCode).toBe(200);
|
||||||
|
expect(resp.body.data.count).toBe(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return error if user has no access to the workflowId specified in filter', async () => {
|
||||||
|
let resp = await authOwnerAgent.get(
|
||||||
|
`/evaluation/test-definitions?filter=${JSON.stringify({ workflowId: otherWorkflow.id })}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resp.statusCode).toBe(403);
|
||||||
|
expect(resp.body.message).toBe('User does not have access to the workflow');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /evaluation/test-definitions/:id', () => {
|
describe('GET /evaluation/test-definitions/:id', () => {
|
||||||
|
|
Loading…
Reference in a new issue