Skip to content

Commit d1d1521

Browse files
committed
fix: add validation, make layout optional, and add tests for HTML layout and watermark fields
1 parent 4d3c547 commit d1d1521

2 files changed

Lines changed: 125 additions & 9 deletions

File tree

src/schemas.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,18 @@ export const HTMLLayoutSchema = z
156156
.union([
157157
PageSizePresetSchema,
158158
z.object({
159-
width: z.number().describe('Page width in millimeters.'),
160-
height: z.number().describe('Page height in millimeters.'),
159+
width: z.number().positive().describe('Page width in millimeters.'),
160+
height: z.number().positive().describe('Page height in millimeters.'),
161161
}),
162162
])
163163
.optional()
164164
.describe('Page size as a preset name (e.g. "A4", "Letter") or custom dimensions in millimeters.'),
165165
margin: z
166166
.object({
167-
left: z.number().describe('Left margin in millimeters.'),
168-
top: z.number().describe('Top margin in millimeters.'),
169-
right: z.number().describe('Right margin in millimeters.'),
170-
bottom: z.number().describe('Bottom margin in millimeters.'),
167+
left: z.number().min(0).describe('Left margin in millimeters.'),
168+
top: z.number().min(0).describe('Top margin in millimeters.'),
169+
right: z.number().min(0).describe('Right margin in millimeters.'),
170+
bottom: z.number().min(0).describe('Bottom margin in millimeters.'),
171171
})
172172
.optional()
173173
.describe('Page margins in millimeters.'),
@@ -186,7 +186,7 @@ export const FilePartSchema = z.object({
186186
.string()
187187
.optional()
188188
.describe("Used to determine the file type when the file content type is not available and can't be inferred."),
189-
layout: HTMLLayoutSchema.describe(
189+
layout: HTMLLayoutSchema.optional().describe(
190190
'Page layout options for HTML-to-PDF conversion. Only applies when the input is an HTML file. ' +
191191
'Supports orientation, page size, and margins.',
192192
),
@@ -269,7 +269,7 @@ export const BaseWatermarkPropertiesSchema = z.object({
269269
bottom: WatermarkDimensionSchema.optional().describe('Offset of the watermark from the bottom edge of a page.'),
270270
left: WatermarkDimensionSchema.optional().describe('Offset of the watermark from the left edge of a page.'),
271271
fontFamily: z.string().optional().describe('The font family to use for text watermarks.'),
272-
fontSize: z.number().optional().describe('Font size in points for text watermarks.'),
272+
fontSize: z.number().positive().optional().describe('Font size in points for text watermarks.'),
273273
fontStyle: z
274274
.array(z.enum(['bold', 'italic']))
275275
.optional()

tests/unit.test.ts

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
22
import fs, { Stats } from 'fs'
33
import { Readable } from 'stream'
4-
import { Instructions, SignatureOptions } from '../src/schemas.js'
4+
import type { Instructions, SignatureOptions } from '../src/schemas.js'
5+
import { BaseWatermarkPropertiesSchema, FilePartSchema } from '../src/schemas.js'
56
import { config as dotenvConfig } from 'dotenv'
67
import { performBuildCall } from '../src/dws/build.js'
78
import { performSignCall } from '../src/dws/sign.js'
@@ -39,6 +40,121 @@ function createMockStream(content: string | Buffer): Readable {
3940
return readable
4041
}
4142

43+
describe('Schema validation', () => {
44+
describe('HTMLLayoutSchema / FilePartSchema', () => {
45+
it('accepts a valid layout with orientation, size, and margin', () => {
46+
const result = FilePartSchema.safeParse({
47+
file: '/test.html',
48+
layout: {
49+
orientation: 'portrait',
50+
size: {
51+
width: 210,
52+
height: 297,
53+
},
54+
margin: {
55+
left: 10,
56+
top: 10,
57+
right: 10,
58+
bottom: 10,
59+
},
60+
},
61+
})
62+
63+
expect(result.success).toBe(true)
64+
})
65+
66+
it('allows omitting layout entirely', () => {
67+
const result = FilePartSchema.safeParse({ file: '/test.html' })
68+
expect(result.success).toBe(true)
69+
})
70+
71+
it('rejects negative margins', () => {
72+
const result = FilePartSchema.safeParse({
73+
file: '/test.html',
74+
layout: {
75+
margin: {
76+
left: -1,
77+
top: 0,
78+
right: 0,
79+
bottom: 0,
80+
},
81+
},
82+
})
83+
84+
expect(result.success).toBe(false)
85+
})
86+
87+
it('rejects zero or negative custom page sizes', () => {
88+
const zeroWidth = FilePartSchema.safeParse({
89+
file: '/test.html',
90+
layout: {
91+
size: {
92+
width: 0,
93+
height: 100,
94+
},
95+
},
96+
})
97+
98+
const negativeHeight = FilePartSchema.safeParse({
99+
file: '/test.html',
100+
layout: {
101+
size: {
102+
width: 100,
103+
height: -1,
104+
},
105+
},
106+
})
107+
108+
expect(zeroWidth.success).toBe(false)
109+
expect(negativeHeight.success).toBe(false)
110+
})
111+
})
112+
113+
describe('Watermark positioning and font fields', () => {
114+
it('accepts valid positioning and font fields', () => {
115+
const result = BaseWatermarkPropertiesSchema.safeParse({
116+
type: 'watermark',
117+
watermarkType: 'text',
118+
width: 100,
119+
height: 50,
120+
top: 10,
121+
right: '5%',
122+
bottom: 12,
123+
left: 8,
124+
text: 'DRAFT',
125+
fontFamily: 'Helvetica',
126+
fontSize: 12,
127+
fontStyle: ['bold', 'italic'],
128+
})
129+
130+
expect(result.success).toBe(true)
131+
})
132+
133+
it('rejects invalid fontSize values (0 or negative)', () => {
134+
const zero = BaseWatermarkPropertiesSchema.safeParse({
135+
type: 'watermark',
136+
watermarkType: 'text',
137+
width: 100,
138+
height: 50,
139+
text: 'DRAFT',
140+
fontSize: 0,
141+
})
142+
143+
const negative = BaseWatermarkPropertiesSchema.safeParse({
144+
type: 'watermark',
145+
watermarkType: 'text',
146+
width: 100,
147+
height: 50,
148+
text: 'DRAFT',
149+
fontSize: -1,
150+
})
151+
152+
expect(zero.success).toBe(false)
153+
expect(negative.success).toBe(false)
154+
})
155+
})
156+
})
157+
42158
describe('API Functions', () => {
43159
const originalEnv = process.env
44160

0 commit comments

Comments
 (0)