Skip to content

Commit 503c84c

Browse files
signed-pdf: Require location for new signature fields
Assisted-by: Codex
1 parent 2e11da2 commit 503c84c

4 files changed

Lines changed: 66 additions & 8 deletions

File tree

src/pdfrest/models/_internal.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,13 @@ class _PdfSignatureConfigurationModel(BaseModel):
637637
location: _PdfSignatureLocationModel | None = None
638638
display: _PdfSignatureDisplayModel | None = None
639639

640+
@model_validator(mode="after")
641+
def _validate_location_for_new_type(self) -> _PdfSignatureConfigurationModel:
642+
if self.type == "new" and self.location is None:
643+
msg = "Missing location information for a new digital signature field."
644+
raise ValueError(msg)
645+
return self
646+
640647

641648
_PdfRedactionVariant = Annotated[
642649
PdfLiteralRedactionModel | PdfRegexRedactionModel | PdfPresetRedactionModel,

src/pdfrest/types/public.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ class PdfSignatureDisplay(TypedDict, total=False):
163163

164164
class PdfSignatureConfiguration(TypedDict, total=False):
165165
type: Required[Literal["new"]]
166+
location: Required[PdfSignatureLocation]
166167
name: str
167168
logo_opacity: float
168-
location: PdfSignatureLocation
169169
display: PdfSignatureDisplay
170170

171171

tests/live/test_live_sign_pdf.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
from ..resources import get_test_resource_path
99

1010

11+
def make_signature_location() -> dict[str, dict[str, int] | int]:
12+
return {
13+
"bottom_left": {"x": 0, "y": 0},
14+
"top_right": {"x": 216, "y": 72},
15+
"page": 1,
16+
}
17+
18+
1119
@pytest.fixture(scope="module")
1220
def uploaded_pdf_for_signing(
1321
pdfrest_api_key: str,
@@ -96,6 +104,7 @@ def test_live_sign_pdf_with_pfx_credentials(
96104
signature_configuration = {
97105
"type": "new",
98106
"name": "pdfrest-live",
107+
"location": make_signature_location(),
99108
}
100109
with PdfRestClient(
101110
api_key=pdfrest_api_key,
@@ -170,7 +179,10 @@ def test_live_sign_pdf_invalid_signature_configuration(
170179
):
171180
client.sign_pdf(
172181
uploaded_pdf_for_signing,
173-
signature_configuration={"type": "new"},
182+
signature_configuration={
183+
"type": "new",
184+
"location": make_signature_location(),
185+
},
174186
credentials={
175187
"pfx": uploaded_pfx_credential,
176188
"passphrase": uploaded_passphrase,

tests/test_sign_pdf.py

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
import httpx
66
import pytest
7+
from pydantic import ValidationError
78

8-
from pdfrest import AsyncPdfRestClient, PdfRestClient, PdfRestConfigurationError
9+
from pdfrest import AsyncPdfRestClient, PdfRestClient
910
from pdfrest.models import PdfRestFile, PdfRestFileBasedResponse, PdfRestFileID
1011
from pdfrest.models._internal import PdfSignPayload
1112

@@ -47,6 +48,14 @@ def make_logo_file(file_id: str) -> PdfRestFile:
4748
)
4849

4950

51+
def make_signature_location() -> dict[str, dict[str, int] | int]:
52+
return {
53+
"bottom_left": {"x": 0, "y": 0},
54+
"top_right": {"x": 216, "y": 72},
55+
"page": 1,
56+
}
57+
58+
5059
def test_sign_pdf_with_pfx_credentials(monkeypatch: pytest.MonkeyPatch) -> None:
5160
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
5261
input_file = make_pdf_file(PdfRestFileID.generate(1))
@@ -57,6 +66,7 @@ def test_sign_pdf_with_pfx_credentials(monkeypatch: pytest.MonkeyPatch) -> None:
5766
signature_configuration = {
5867
"type": "new",
5968
"name": "esignature",
69+
"location": make_signature_location(),
6070
"display": {"include_datetime": True, "name": "Signer"},
6171
}
6272
payload_dump = PdfSignPayload.model_validate(
@@ -183,27 +193,53 @@ def test_sign_pdf_requires_credential_pair(
183193
monkeypatch: pytest.MonkeyPatch,
184194
) -> None:
185195
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
186-
input_file = make_pdf_file(PdfRestFileID.generate(3))
196+
input_file = make_pdf_file(PdfRestFileID.generate())
187197
pfx_file = make_pfx_file(str(PdfRestFileID.generate()))
188198
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
189199

190200
with (
191201
PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client,
192-
pytest.raises(PdfRestConfigurationError, match=r"pfx.*passphrase"),
202+
pytest.raises(ValidationError, match=r"pfx.*passphrase"),
193203
):
194204
client.sign_pdf(
195205
input_file,
196-
signature_configuration={"type": "new"},
206+
signature_configuration={
207+
"type": "new",
208+
"location": make_signature_location(),
209+
},
197210
credentials={"pfx": pfx_file},
198211
)
199212

200213

214+
def test_sign_pdf_requires_location_for_new_signature_type(
215+
monkeypatch: pytest.MonkeyPatch,
216+
) -> None:
217+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
218+
input_file = make_pdf_file(PdfRestFileID.generate())
219+
pfx_file = make_pfx_file(str(PdfRestFileID.generate()))
220+
passphrase_file = make_passphrase_file(str(PdfRestFileID.generate()))
221+
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
222+
223+
with (
224+
PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client,
225+
pytest.raises(
226+
ValidationError,
227+
match=r"Missing location information for a new digital signature field",
228+
),
229+
):
230+
client.sign_pdf(
231+
input_file,
232+
signature_configuration={"type": "new"},
233+
credentials={"pfx": pfx_file, "passphrase": passphrase_file},
234+
)
235+
236+
201237
@pytest.mark.asyncio
202238
async def test_async_sign_pdf_request_customization(
203239
monkeypatch: pytest.MonkeyPatch,
204240
) -> None:
205241
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
206-
input_file = make_pdf_file(PdfRestFileID.generate(4))
242+
input_file = make_pdf_file(PdfRestFileID.generate())
207243
certificate_file = make_certificate_file(str(PdfRestFileID.generate()))
208244
private_key_file = make_private_key_file(str(PdfRestFileID.generate()))
209245
output_id = str(PdfRestFileID.generate())
@@ -245,7 +281,10 @@ def handler(request: httpx.Request) -> httpx.Response:
245281
) as client:
246282
response = await client.sign_pdf(
247283
input_file,
248-
signature_configuration={"type": "new"},
284+
signature_configuration={
285+
"type": "new",
286+
"location": make_signature_location(),
287+
},
249288
credentials={
250289
"certificate": certificate_file,
251290
"private_key": private_key_file,

0 commit comments

Comments
 (0)