mailsocket 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mailsocket-0.1.2 → mailsocket-0.1.3}/PKG-INFO +9 -3
- {mailsocket-0.1.2 → mailsocket-0.1.3}/README.md +8 -2
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket/__init__.py +1 -1
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket/client.py +13 -7
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket.egg-info/PKG-INFO +9 -3
- {mailsocket-0.1.2 → mailsocket-0.1.3}/pyproject.toml +1 -1
- {mailsocket-0.1.2 → mailsocket-0.1.3}/tests/test_client.py +72 -4
- {mailsocket-0.1.2 → mailsocket-0.1.3}/LICENSE +0 -0
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket/errors.py +0 -0
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket.egg-info/SOURCES.txt +0 -0
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket.egg-info/dependency_links.txt +0 -0
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket.egg-info/requires.txt +0 -0
- {mailsocket-0.1.2 → mailsocket-0.1.3}/mailsocket.egg-info/top_level.txt +0 -0
- {mailsocket-0.1.2 → mailsocket-0.1.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailsocket
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Official Python client for the mailsocket REST API — one line to wait for an OTP.
|
|
5
5
|
Author: mailsocket
|
|
6
6
|
License-Expression: MIT
|
|
@@ -116,5 +116,11 @@ cd python
|
|
|
116
116
|
python -m pytest -q
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Tests
|
|
120
|
-
scripted responder,
|
|
119
|
+
Tests make no external or live-API requests. Most monkeypatch
|
|
120
|
+
`urllib.request.urlopen` with a scripted responder, and a few start a real local
|
|
121
|
+
`http.server` on `127.0.0.1` to check actual request behaviour, e.g. that an
|
|
122
|
+
invalid id never reaches the network.
|
|
123
|
+
|
|
124
|
+
## Changelog
|
|
125
|
+
|
|
126
|
+
- **0.1.3** — `_seg` now rejects an empty id or a bare `.`/`..` id with a `MailsocketError` (`code="invalid_id"`) before any request is made, matching the TypeScript SDK's contract.
|
|
@@ -90,5 +90,11 @@ cd python
|
|
|
90
90
|
python -m pytest -q
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
Tests
|
|
94
|
-
scripted responder,
|
|
93
|
+
Tests make no external or live-API requests. Most monkeypatch
|
|
94
|
+
`urllib.request.urlopen` with a scripted responder, and a few start a real local
|
|
95
|
+
`http.server` on `127.0.0.1` to check actual request behaviour, e.g. that an
|
|
96
|
+
invalid id never reaches the network.
|
|
97
|
+
|
|
98
|
+
## Changelog
|
|
99
|
+
|
|
100
|
+
- **0.1.3** — `_seg` now rejects an empty id or a bare `.`/`..` id with a `MailsocketError` (`code="invalid_id"`) before any request is made, matching the TypeScript SDK's contract.
|
|
@@ -32,13 +32,19 @@ def _sleep(seconds: float) -> None:
|
|
|
32
32
|
def _seg(value) -> str:
|
|
33
33
|
"""Percent-encode an id as ONE path segment (``/``, ``?``, ``#``, ``..`` safe).
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
An empty id and the bare dot segments ``.`` / ``..`` are rejected with a
|
|
36
|
+
:class:`MailsocketError` before any request is made (same contract as the
|
|
37
|
+
TypeScript SDK's ``seg``): urllib's request machinery would otherwise let
|
|
38
|
+
them collapse into a different path than the caller intended. No real id
|
|
39
|
+
is ever one of these.
|
|
37
40
|
"""
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
raw = str(value)
|
|
42
|
+
if raw in ("", ".", ".."):
|
|
43
|
+
raise MailsocketError(
|
|
44
|
+
f"Invalid id {raw!r}: ids must be non-empty and not \".\" or \"..\".",
|
|
45
|
+
code="invalid_id",
|
|
46
|
+
)
|
|
47
|
+
return urllib.parse.quote(raw, safe="")
|
|
42
48
|
|
|
43
49
|
|
|
44
50
|
class Page:
|
|
@@ -282,7 +288,7 @@ class Client:
|
|
|
282
288
|
url += "?" + urllib.parse.urlencode(params, doseq=True)
|
|
283
289
|
headers = {
|
|
284
290
|
"Accept": "application/json",
|
|
285
|
-
"User-Agent": "mailsocket-python/0.1.
|
|
291
|
+
"User-Agent": "mailsocket-python/0.1.3",
|
|
286
292
|
}
|
|
287
293
|
headers.update(self._extra_headers)
|
|
288
294
|
# Set last so nothing in extra_headers can ever replace it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailsocket
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Official Python client for the mailsocket REST API — one line to wait for an OTP.
|
|
5
5
|
Author: mailsocket
|
|
6
6
|
License-Expression: MIT
|
|
@@ -116,5 +116,11 @@ cd python
|
|
|
116
116
|
python -m pytest -q
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Tests
|
|
120
|
-
scripted responder,
|
|
119
|
+
Tests make no external or live-API requests. Most monkeypatch
|
|
120
|
+
`urllib.request.urlopen` with a scripted responder, and a few start a real local
|
|
121
|
+
`http.server` on `127.0.0.1` to check actual request behaviour, e.g. that an
|
|
122
|
+
invalid id never reaches the network.
|
|
123
|
+
|
|
124
|
+
## Changelog
|
|
125
|
+
|
|
126
|
+
- **0.1.3** — `_seg` now rejects an empty id or a bare `.`/`..` id with a `MailsocketError` (`code="invalid_id"`) before any request is made, matching the TypeScript SDK's contract.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mailsocket"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "Official Python client for the mailsocket REST API — one line to wait for an OTP."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -203,8 +203,6 @@ HOSTILE_IDS = [
|
|
|
203
203
|
("in?x=1", "in%3Fx%3D1"),
|
|
204
204
|
("in#frag", "in%23frag"),
|
|
205
205
|
("../../admin", "..%2F..%2Fadmin"),
|
|
206
|
-
("..", "%2E%2E"),
|
|
207
|
-
(".", "%2E"),
|
|
208
206
|
("a/b", "a%2Fb"),
|
|
209
207
|
("sp ce&%", "sp%20ce%26%25"),
|
|
210
208
|
]
|
|
@@ -437,18 +435,88 @@ def test_error_types_are_mailsocket_subclasses():
|
|
|
437
435
|
assert issubclass(WaitTimeout, MailsocketError)
|
|
438
436
|
|
|
439
437
|
|
|
438
|
+
# -- F2: _seg rejects "" / "." / ".." before any request (parity with TS) -----
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def test_seg_rejects_empty_and_dot_segments():
|
|
442
|
+
for raw in ("", ".", ".."):
|
|
443
|
+
with pytest.raises(MailsocketError) as exc_info:
|
|
444
|
+
mailsocket.client._seg(raw)
|
|
445
|
+
assert exc_info.value.code == "invalid_id"
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
INVALID_IDS = ["", ".", ".."]
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@pytest.mark.parametrize("raw", INVALID_IDS)
|
|
452
|
+
def test_invalid_id_rejected_before_any_request_hits_real_server(raw):
|
|
453
|
+
"""Nothing reaches the network: a real local http.server never sees a request."""
|
|
454
|
+
import http.server
|
|
455
|
+
import threading
|
|
456
|
+
|
|
457
|
+
seen = []
|
|
458
|
+
|
|
459
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
460
|
+
def _record(self):
|
|
461
|
+
seen.append(self.path)
|
|
462
|
+
self.send_response(200)
|
|
463
|
+
self.send_header("Content-Type", "application/json")
|
|
464
|
+
self.end_headers()
|
|
465
|
+
self.wfile.write(json.dumps({"data": {"id": "x"}}).encode())
|
|
466
|
+
|
|
467
|
+
def do_GET(self):
|
|
468
|
+
self._record()
|
|
469
|
+
|
|
470
|
+
def do_DELETE(self):
|
|
471
|
+
self._record()
|
|
472
|
+
|
|
473
|
+
def do_POST(self):
|
|
474
|
+
self._record()
|
|
475
|
+
|
|
476
|
+
def log_message(self, format, *args):
|
|
477
|
+
pass # keep test output clean
|
|
478
|
+
|
|
479
|
+
server = http.server.HTTPServer(("127.0.0.1", 0), Handler)
|
|
480
|
+
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
481
|
+
thread.start()
|
|
482
|
+
try:
|
|
483
|
+
base_url = f"http://127.0.0.1:{server.server_port}/api/v1"
|
|
484
|
+
client = Client("ms_live_test1234567890", base_url=base_url)
|
|
485
|
+
|
|
486
|
+
calls = [
|
|
487
|
+
lambda: client.get_inbox(raw),
|
|
488
|
+
lambda: client.delete_inbox(raw),
|
|
489
|
+
lambda: client.list_messages(raw),
|
|
490
|
+
lambda: client.get_latest(raw),
|
|
491
|
+
lambda: client.get_message(raw),
|
|
492
|
+
lambda: client.wait_for_otp(raw, timeout=5),
|
|
493
|
+
lambda: client.wait_for_link(raw, timeout=5),
|
|
494
|
+
lambda: client.wait(raw, timeout=5),
|
|
495
|
+
]
|
|
496
|
+
for call in calls:
|
|
497
|
+
with pytest.raises(MailsocketError) as exc_info:
|
|
498
|
+
call()
|
|
499
|
+
assert exc_info.value.code == "invalid_id"
|
|
500
|
+
assert not isinstance(exc_info.value, WaitTimeout)
|
|
501
|
+
|
|
502
|
+
assert seen == [] # nothing ever reached the network
|
|
503
|
+
finally:
|
|
504
|
+
server.shutdown()
|
|
505
|
+
thread.join(timeout=5)
|
|
506
|
+
|
|
507
|
+
|
|
440
508
|
def test_version_exported():
|
|
441
509
|
assert hasattr(mailsocket, "__version__")
|
|
442
510
|
|
|
443
511
|
|
|
444
512
|
def test_metadata_consistency_across_files():
|
|
445
|
-
"""0.1.
|
|
513
|
+
"""0.1.3 must match everywhere: pyproject, __version__, user agent."""
|
|
446
514
|
import re
|
|
447
515
|
import tomllib
|
|
448
516
|
from pathlib import Path
|
|
449
517
|
|
|
450
518
|
root = Path(__file__).resolve().parents[1]
|
|
451
|
-
expected = "0.1.
|
|
519
|
+
expected = "0.1.3"
|
|
452
520
|
|
|
453
521
|
pyproject = tomllib.loads((root / "pyproject.toml").read_text())
|
|
454
522
|
assert pyproject["project"]["version"] == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|