feat: Forbid creating active workflows (no-changelog) (#22718)

This commit is contained in:
Daria
2025-12-04 12:44:02 +02:00
committed by GitHub
parent 14205dc0c6
commit 9a72da03fd
4 changed files with 57 additions and 30 deletions
@@ -1,4 +1,5 @@
import type { ImportWorkflowFromUrlDto } from '@n8n/api-types';
import type { Logger } from '@n8n/backend-common';
import type { AuthenticatedRequest, IExecutionResponse, CredentialsEntity, User } from '@n8n/db';
import { WorkflowEntity } from '@n8n/db';
import axios from 'axios';
@@ -25,9 +26,11 @@ describe('WorkflowsController', () => {
const req = mock<AuthenticatedRequest>();
const res = mock<Response>();
const projectService = mock<ProjectService>();
const logger = mock<Logger>();
beforeEach(() => {
controller.projectService = projectService;
controller.logger = logger;
jest.clearAllMocks();
});
@@ -96,6 +96,18 @@ export class WorkflowsController {
// mess with relations of other workflows
delete req.body.shared;
// @ts-expect-error: We shouldn't accept this, this will be set when activating
if (req.body.activeVersionId || req.body.active) {
this.logger.warn(
'Creating a workflow as active is not supported. The workflow will be created as inactive.',
{ userId: req.user.id },
);
// @ts-expect-error: We shouldn't accept this
req.body.activeVersionId = undefined;
req.body.active = false;
}
const newWorkflow = new WorkflowEntity();
Object.assign(newWorkflow, req.body);
@@ -1,5 +1,5 @@
import { createWorkflow, testDb } from '@n8n/backend-test-utils';
import type { User } from '@n8n/db';
import type { User, WorkflowHistory } from '@n8n/db';
import { createOwner, createUser } from './shared/db/users';
import { createWorkflowHistoryItem } from './shared/db/workflow-history';
@@ -149,31 +149,52 @@ describe('GET /workflow-history/:workflowId', () => {
expect(resp.body.data).toHaveLength(5);
expect(resp.body.data[0]).toEqual(last);
});
test('should include workflowPublishHistory records related to each history item', async () => {
const workflow = await createWorkflow(undefined, owner);
const v1 = await createWorkflowHistoryItem(workflow.id);
const v2 = await createWorkflowHistoryItem(workflow.id);
const wph1 = await createWorkflowPublishHistoryItem(v1);
const wph2 = await createWorkflowPublishHistoryItem(v1, { event: 'deactivated' });
const wph3 = await createWorkflowPublishHistoryItem(v2);
const resp = await authOwnerAgent.get(`/workflow-history/workflow/${workflow.id}`);
const response = await authOwnerAgent.get(`/workflow-history/workflow/${workflow.id}`);
expect(response.status).toBe(200);
expect(resp.status).toBe(200);
expect(resp.body.data[0].workflowPublishHistory).toHaveLength(1);
expect(resp.body.data[0].workflowPublishHistory).toContainEqual({
...wph3,
createdAt: wph3.createdAt.toISOString(),
});
expect(resp.body.data[1].workflowPublishHistory).toHaveLength(2);
expect(resp.body.data[1].workflowPublishHistory).toContainEqual({
...wph1,
createdAt: wph1.createdAt.toISOString(),
});
expect(resp.body.data[1].workflowPublishHistory).toContainEqual({
...wph2,
createdAt: wph2.createdAt.toISOString(),
});
const body = response.body as { data: WorkflowHistory[] };
expect(body.data).toHaveLength(2);
const publishHistories = body.data.map((history) => history.workflowPublishHistory);
expect(publishHistories).toEqual(
expect.arrayContaining([expect.any(Array), expect.any(Array)]),
);
const publishHistory1 = publishHistories.find((ph) => ph.length === 1)!;
const publishHistory2 = publishHistories.find((ph) => ph.length === 2)!;
expect(publishHistory1).toEqual(
expect.arrayContaining([
expect.objectContaining({
...wph3,
createdAt: wph3.createdAt.toISOString(),
}),
]),
);
expect(publishHistory2).toEqual(
expect.arrayContaining([
expect.objectContaining({
...wph1,
createdAt: wph1.createdAt.toISOString(),
}),
expect.objectContaining({
...wph2,
createdAt: wph2.createdAt.toISOString(),
}),
]),
);
});
});
@@ -255,7 +255,7 @@ describe('POST /workflows', () => {
expect(historyVersion!.nodes).toEqual(payload.nodes);
});
test('should create workflow as active when active: true is provided in POST body', async () => {
test('should create workflow as inactive even when active: true is provided in POST body', async () => {
const payload = {
name: 'active workflow',
nodes: [
@@ -284,21 +284,12 @@ describe('POST /workflows', () => {
expect(id).toBeDefined();
expect(versionId).toBeDefined();
expect(activeVersionId).toBe(versionId); // Should be set to current version
expect(active).toBe(true);
expect(activeVersionId).toBeNull();
expect(active).toBe(false);
// Verify in database
const workflow = await workflowRepository.findOneBy({ id });
expect(workflow?.activeVersionId).toBe(versionId);
// Verify history was created
const historyVersion = await workflowHistoryRepository.findOne({
where: {
workflowId: id,
versionId,
},
});
expect(historyVersion).not.toBeNull();
expect(workflow?.activeVersionId).toBeNull();
});
test('create workflow in personal project by default', async () => {