Skip to content

Commit 92da139

Browse files
tests: Add validation for handling multiple input, logo, or credential files
- Introduced validation tests in `test_sign_pdf.py` to ensure rejection of multiple input PDFs, logos, and credential files during PDF signing. - Added corresponding async validation tests for enhanced API behavior coverage. - Verified HTTP request counts (`POST` and `GET`) in sync and async test cases. Assisted-by: Codex
1 parent d71ec95 commit 92da139

1 file changed

Lines changed: 173 additions & 0 deletions

File tree

tests/test_sign_pdf.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def test_sign_pdf_with_pfx_credentials(monkeypatch: pytest.MonkeyPatch) -> None:
7272
pfx_file = make_pfx_file(str(PdfRestFileID.generate()))
7373
passphrase_file = make_passphrase_file(str(PdfRestFileID.generate()))
7474
output_id = str(PdfRestFileID.generate())
75+
seen = {"post": 0, "get": 0}
7576

7677
signature_configuration = {
7778
"type": "new",
@@ -90,6 +91,7 @@ def test_sign_pdf_with_pfx_credentials(monkeypatch: pytest.MonkeyPatch) -> None:
9091

9192
def handler(request: httpx.Request) -> httpx.Response:
9293
if request.method == "POST" and request.url.path == "/signed-pdf":
94+
seen["post"] += 1
9395
payload = json.loads(request.content.decode("utf-8"))
9496
assert payload == payload_dump
9597
return httpx.Response(
@@ -100,6 +102,7 @@ def handler(request: httpx.Request) -> httpx.Response:
100102
},
101103
)
102104
if request.method == "GET" and request.url.path == f"/resource/{output_id}":
105+
seen["get"] += 1
103106
assert request.url.params["format"] == "info"
104107
return httpx.Response(
105108
200,
@@ -124,6 +127,7 @@ def handler(request: httpx.Request) -> httpx.Response:
124127
assert isinstance(response, PdfRestFileBasedResponse)
125128
assert response.output_file.name == "signed-pdf.pdf"
126129
assert str(input_file.id) in {str(item) for item in response.input_ids}
130+
assert seen == {"post": 1, "get": 1}
127131

128132

129133
def test_sign_pdf_with_certificate_credentials_and_logo(
@@ -135,6 +139,7 @@ def test_sign_pdf_with_certificate_credentials_and_logo(
135139
private_key_file = make_private_key_file(str(PdfRestFileID.generate()))
136140
logo_file = make_logo_file(str(PdfRestFileID.generate()))
137141
output_id = str(PdfRestFileID.generate())
142+
seen = {"post": 0, "get": 0}
138143

139144
signature_configuration = {
140145
"type": "new",
@@ -159,6 +164,7 @@ def test_sign_pdf_with_certificate_credentials_and_logo(
159164

160165
def handler(request: httpx.Request) -> httpx.Response:
161166
if request.method == "POST" and request.url.path == "/signed-pdf":
167+
seen["post"] += 1
162168
payload = json.loads(request.content.decode("utf-8"))
163169
assert payload == payload_dump
164170
return httpx.Response(
@@ -174,6 +180,7 @@ def handler(request: httpx.Request) -> httpx.Response:
174180
},
175181
)
176182
if request.method == "GET" and request.url.path == f"/resource/{output_id}":
183+
seen["get"] += 1
177184
return httpx.Response(
178185
200,
179186
json=build_file_info_payload(
@@ -198,6 +205,7 @@ def handler(request: httpx.Request) -> httpx.Response:
198205
assert isinstance(response, PdfRestFileBasedResponse)
199206
assert response.output_file.name == "signed.pdf"
200207
assert logo_file.id in response.input_ids
208+
assert seen == {"post": 1, "get": 1}
201209

202210

203211
def test_sign_pdf_requires_credential_pair(
@@ -266,6 +274,86 @@ def test_sign_pdf_requires_location_for_new_signature_type(
266274
)
267275

268276

277+
def test_sign_pdf_rejects_multiple_input_files(
278+
monkeypatch: pytest.MonkeyPatch,
279+
) -> None:
280+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
281+
input_file_a = make_pdf_file(PdfRestFileID.generate())
282+
input_file_b = make_pdf_file(PdfRestFileID.generate())
283+
pfx_file = make_pfx_file(str(PdfRestFileID.generate()))
284+
passphrase_file = make_passphrase_file(str(PdfRestFileID.generate()))
285+
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
286+
287+
with (
288+
PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client,
289+
pytest.raises(ValidationError, match=r"files|at most 1 item"),
290+
):
291+
client.sign_pdf(
292+
[input_file_a, input_file_b],
293+
signature_configuration={
294+
"type": "new",
295+
"location": make_signature_location(),
296+
},
297+
credentials={"pfx": pfx_file, "passphrase": passphrase_file},
298+
)
299+
300+
301+
def test_sign_pdf_rejects_multiple_logo_files(
302+
monkeypatch: pytest.MonkeyPatch,
303+
) -> None:
304+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
305+
input_file = make_pdf_file(PdfRestFileID.generate())
306+
certificate_file = make_certificate_file(str(PdfRestFileID.generate()))
307+
private_key_file = make_private_key_file(str(PdfRestFileID.generate()))
308+
logo_file_a = make_logo_file(str(PdfRestFileID.generate()))
309+
logo_file_b = make_logo_file(str(PdfRestFileID.generate()))
310+
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
311+
312+
with (
313+
PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client,
314+
pytest.raises(ValidationError, match=r"logo|at most 1 item"),
315+
):
316+
client.sign_pdf(
317+
input_file,
318+
signature_configuration={
319+
"type": "new",
320+
"location": make_signature_location(),
321+
},
322+
credentials={
323+
"certificate": certificate_file,
324+
"private_key": private_key_file,
325+
},
326+
logo=[logo_file_a, logo_file_b],
327+
)
328+
329+
330+
def test_sign_pdf_rejects_multiple_pfx_credential_files(
331+
monkeypatch: pytest.MonkeyPatch,
332+
) -> None:
333+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
334+
input_file = make_pdf_file(PdfRestFileID.generate())
335+
pfx_file_a = make_pfx_file(str(PdfRestFileID.generate()))
336+
pfx_file_b = make_pfx_file(str(PdfRestFileID.generate()))
337+
passphrase_file = make_passphrase_file(str(PdfRestFileID.generate()))
338+
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
339+
340+
with (
341+
PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client,
342+
pytest.raises(ValidationError, match=r"pfx|at most 1 item"),
343+
):
344+
client.sign_pdf(
345+
input_file,
346+
signature_configuration={
347+
"type": "new",
348+
"location": make_signature_location(),
349+
},
350+
credentials={
351+
"pfx": [pfx_file_a, pfx_file_b], # type: ignore[list-item]
352+
"passphrase": passphrase_file,
353+
},
354+
)
355+
356+
269357
@pytest.mark.asyncio
270358
async def test_async_sign_pdf_requires_credential_pair(
271359
monkeypatch: pytest.MonkeyPatch,
@@ -329,6 +417,83 @@ async def test_async_sign_pdf_requires_location_for_new_signature_type(
329417
)
330418

331419

420+
@pytest.mark.asyncio
421+
async def test_async_sign_pdf_rejects_multiple_input_files(
422+
monkeypatch: pytest.MonkeyPatch,
423+
) -> None:
424+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
425+
input_file_a = make_pdf_file(PdfRestFileID.generate())
426+
input_file_b = make_pdf_file(PdfRestFileID.generate())
427+
pfx_file = make_pfx_file(str(PdfRestFileID.generate()))
428+
passphrase_file = make_passphrase_file(str(PdfRestFileID.generate()))
429+
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
430+
431+
async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client:
432+
with pytest.raises(ValidationError, match=r"files|at most 1 item"):
433+
await client.sign_pdf(
434+
[input_file_a, input_file_b],
435+
signature_configuration={
436+
"type": "new",
437+
"location": make_signature_location(),
438+
},
439+
credentials={"pfx": pfx_file, "passphrase": passphrase_file},
440+
)
441+
442+
443+
@pytest.mark.asyncio
444+
async def test_async_sign_pdf_rejects_multiple_logo_files(
445+
monkeypatch: pytest.MonkeyPatch,
446+
) -> None:
447+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
448+
input_file = make_pdf_file(PdfRestFileID.generate())
449+
certificate_file = make_certificate_file(str(PdfRestFileID.generate()))
450+
private_key_file = make_private_key_file(str(PdfRestFileID.generate()))
451+
logo_file_a = make_logo_file(str(PdfRestFileID.generate()))
452+
logo_file_b = make_logo_file(str(PdfRestFileID.generate()))
453+
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
454+
455+
async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client:
456+
with pytest.raises(ValidationError, match=r"logo|at most 1 item"):
457+
await client.sign_pdf(
458+
input_file,
459+
signature_configuration={
460+
"type": "new",
461+
"location": make_signature_location(),
462+
},
463+
credentials={
464+
"certificate": certificate_file,
465+
"private_key": private_key_file,
466+
},
467+
logo=[logo_file_a, logo_file_b],
468+
)
469+
470+
471+
@pytest.mark.asyncio
472+
async def test_async_sign_pdf_rejects_multiple_pfx_credential_files(
473+
monkeypatch: pytest.MonkeyPatch,
474+
) -> None:
475+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
476+
input_file = make_pdf_file(PdfRestFileID.generate())
477+
pfx_file_a = make_pfx_file(str(PdfRestFileID.generate()))
478+
pfx_file_b = make_pfx_file(str(PdfRestFileID.generate()))
479+
passphrase_file = make_passphrase_file(str(PdfRestFileID.generate()))
480+
transport = httpx.MockTransport(lambda request: (_ for _ in ()).throw(RuntimeError))
481+
482+
async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client:
483+
with pytest.raises(ValidationError, match=r"pfx|at most 1 item"):
484+
await client.sign_pdf(
485+
input_file,
486+
signature_configuration={
487+
"type": "new",
488+
"location": make_signature_location(),
489+
},
490+
credentials={
491+
"pfx": [pfx_file_a, pfx_file_b], # type: ignore[list-item]
492+
"passphrase": passphrase_file,
493+
},
494+
)
495+
496+
332497
@pytest.mark.parametrize(
333498
"signature_type",
334499
[
@@ -437,9 +602,11 @@ def test_sign_pdf_request_customization(
437602
private_key_file = make_private_key_file(str(PdfRestFileID.generate()))
438603
output_id = str(PdfRestFileID.generate())
439604
captured_timeout: dict[str, float | dict[str, float] | None] = {}
605+
seen = {"post": 0, "get": 0}
440606

441607
def handler(request: httpx.Request) -> httpx.Response:
442608
if request.method == "POST" and request.url.path == "/signed-pdf":
609+
seen["post"] += 1
443610
assert request.url.params["trace"] == "sync"
444611
assert request.headers["X-Debug"] == "sync-sign"
445612
captured_timeout["value"] = request.extensions.get("timeout")
@@ -454,6 +621,7 @@ def handler(request: httpx.Request) -> httpx.Response:
454621
json={"inputId": [input_file.id], "outputId": [output_id]},
455622
)
456623
if request.method == "GET" and request.url.path == f"/resource/{output_id}":
624+
seen["get"] += 1
457625
assert request.url.params["trace"] == "sync"
458626
assert request.headers["X-Debug"] == "sync-sign"
459627
return httpx.Response(
@@ -494,6 +662,7 @@ def handler(request: httpx.Request) -> httpx.Response:
494662
assert all(pytest.approx(0.5) == value for value in timeout_value.values())
495663
else:
496664
assert timeout_value == pytest.approx(0.5)
665+
assert seen == {"post": 1, "get": 1}
497666

498667

499668
@pytest.mark.asyncio
@@ -506,9 +675,11 @@ async def test_async_sign_pdf_request_customization(
506675
private_key_file = make_private_key_file(str(PdfRestFileID.generate()))
507676
output_id = str(PdfRestFileID.generate())
508677
captured_timeout: dict[str, float | dict[str, float] | None] = {}
678+
seen = {"post": 0, "get": 0}
509679

510680
def handler(request: httpx.Request) -> httpx.Response:
511681
if request.method == "POST" and request.url.path == "/signed-pdf":
682+
seen["post"] += 1
512683
assert request.url.params["trace"] == "async"
513684
assert request.headers["X-Debug"] == "async-sign"
514685
captured_timeout["value"] = request.extensions.get("timeout")
@@ -523,6 +694,7 @@ def handler(request: httpx.Request) -> httpx.Response:
523694
json={"inputId": [input_file.id], "outputId": [output_id]},
524695
)
525696
if request.method == "GET" and request.url.path == f"/resource/{output_id}":
697+
seen["get"] += 1
526698
assert request.url.params["trace"] == "async"
527699
assert request.headers["X-Debug"] == "async-sign"
528700
return httpx.Response(
@@ -566,6 +738,7 @@ def handler(request: httpx.Request) -> httpx.Response:
566738
assert all(pytest.approx(0.5) == value for value in timeout_value.values())
567739
else:
568740
assert timeout_value == pytest.approx(0.5)
741+
assert seen == {"post": 1, "get": 1}
569742

570743

571744
def test_sign_payload_requires_location_when_type_new() -> None:

0 commit comments

Comments
 (0)