fix(API): Accept workflow descriptions on creation (#39705)

Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
This commit is contained in:
n8n-cat-bot[bot] 2026-09-28 15:48:16 +00:00 committed by GitHub
parent 5676882e64
commit 6c1edac204
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 48 additions and 10 deletions

View File

@ -17,6 +17,22 @@ const validPayload = {
};
describe('CreateWorkflowPublicDto', () => {
test('accepts an optional workflow description', () => {
const payload = { ...validPayload, description: 'What this workflow does' };
expect(CreateWorkflowPublicDto.safeParse(payload)).toMatchObject({
success: true,
data: { description: payload.description },
});
expect(CreateWorkflowPublicDto.safeParse(validPayload).success).toBe(true);
});
test('rejects a non-string workflow description', () => {
expect(CreateWorkflowPublicDto.safeParse({ ...validPayload, description: 42 }).success).toBe(
false,
);
});
test('rejects an unknown key through both the DTO and its schema', () => {
const payload = { ...validPayload, notAWorkflowField: 'x' };

View File

@ -151,13 +151,7 @@ const staticDataWritePublicSchema = z
.nullable()
.openapi(workflowCreateFieldDocs.staticData);
/**
* The public workflow write surface, shared by the create and update request bodies.
*
* `CreateWorkflowPublicDto` uses it as-is. `UpdateWorkflowPublicDto` omits `projectId` from its
* shape, since a project is only chosen when a workflow is created, and adds `description`, which
* only the update body has ever accepted.
*/
// Each route adds `description` to its own DTO so their request schemas stay independent.
export const workflowWritePublicShape = {
id: readOnlyPublicSchema(workflowCreateReadOnlyFieldDocs.id),
name: z.string().openapi(workflowCreateFieldDocs.name),

View File

@ -1,6 +1,13 @@
import { z } from 'zod';
import { workflowWritePublicShape } from './base-workflow-public.dto';
import { workflowCreateFieldDocs } from './workflow-public.openapi';
import { Z } from '../../zod-class';
export class CreateWorkflowPublicDto extends Z.class(workflowWritePublicShape, {
strict: true,
}) {}
export class CreateWorkflowPublicDto extends Z.class(
{
...workflowWritePublicShape,
description: z.string().optional().openapi(workflowCreateFieldDocs.description),
},
{ strict: true },
) {}

View File

@ -103,6 +103,10 @@ export const workflowSettingsFieldDocs = {
export const workflowCreateFieldDocs = {
name: { example: 'Workflow 1' },
description: {
description: 'Description of the workflow',
example: 'My workflow description',
},
nodes: { description: 'Nodes that make up the workflow' },
nodeGroups: { description: 'Visual groupings of nodes shown as frames on the canvas' },
staticData: { description: 'Data the workflow keeps between executions', example: { lastId: 1 } },

View File

@ -553,6 +553,10 @@ requestBody:
type: string
format: date-time
readOnly: true
description:
type: string
description: Description of the workflow
example: My workflow description
required:
- name
- nodes

View File

@ -2202,6 +2202,19 @@ describe('POST /workflows', () => {
expect(sharedWorkflow?.role).toEqual('workflow:owner');
});
test('should create a workflow with a description', async () => {
const description = 'What this workflow does';
const response = await authMemberAgent.post('/workflows').send({
...mockPostWorkflowPayload(),
description,
});
expect(response.statusCode).toBe(200);
expect(response.body.description).toBe(description);
const stored = await workflowRepository.findOneBy({ id: response.body.id });
expect(stored?.description).toBe(description);
});
test.each([
{ key: 'binaryMode', value: 'combined' },
{ key: 'credentialResolverId', value: 'some-resolver-id' },