Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/api/src/policies/dto/create-policy.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ValidationPipe } from '@nestjs/common';
import { CreatePolicyDto } from './create-policy.dto';
import { UpdatePolicyDto } from './update-policy.dto';
import { UpdateVersionContentDto } from './version.dto';

/**
* Regression test for the MCP/public-API policy content serialization bug.
Expand Down Expand Up @@ -62,6 +63,20 @@ describe('Policy DTO content serialization (ValidationPipe)', () => {
expect(result.content).not.toEqual([[], []]);
});

// PATCH /v1/policies/:id/versions/:versionId (MCP: update-policy-version-content).
// The controller reads req.body today, but this DTO is the published body
// schema — if it is ever wired to @Body() its transform must survive the pipe.
it('preserves structured TipTap content on UpdateVersionContentDto', async () => {
const result = await pipe.transform(
{ content: TIPTAP_NODES },
{ type: 'body', metatype: UpdateVersionContentDto },
);

expect(result.content).toEqual(TIPTAP_NODES);
expect(result.content[0]).toMatchObject({ type: 'heading' });
expect(result.content).not.toEqual([[], []]);
});

it('leaves content untouched when omitted on update', async () => {
const result = await pipe.transform(
{ name: 'Renamed only' },
Expand Down
8 changes: 6 additions & 2 deletions apps/api/src/policies/dto/version.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ export class UpdateVersionContentDto {
type: 'array',
items: { type: 'object', additionalProperties: true },
})
@Transform(({ value }) => value) // Preserve raw JSON, don't let class-transformer mangle it
@IsArray()
@Transform(({ value }) => value)
// Return the raw source value. Under the global ValidationPipe's implicit
// conversion, class-transformer coerces each TipTap node toward the reflected
// Array design-type of `content`, mangling `[{...}, {...}]` into `[[], []]`.
// The transform runs after that coercion, so `value` is already mangled —
// `obj.content` is the untouched original. Do not revert this to `value`.
@Transform(({ obj }) => obj.content)
content: unknown[];
}

Expand Down
Loading
Loading