mailsocket 0.1.3__tar.gz → 0.2.0__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.3 → mailsocket-0.2.0}/PKG-INFO +20 -7
- {mailsocket-0.1.3 → mailsocket-0.2.0}/README.md +18 -6
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket/__init__.py +1 -1
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket/client.py +45 -13
- mailsocket-0.2.0/mailsocket/py.typed +0 -0
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket.egg-info/PKG-INFO +20 -7
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket.egg-info/SOURCES.txt +1 -0
- {mailsocket-0.1.3 → mailsocket-0.2.0}/pyproject.toml +5 -1
- {mailsocket-0.1.3 → mailsocket-0.2.0}/tests/test_client.py +67 -2
- {mailsocket-0.1.3 → mailsocket-0.2.0}/LICENSE +0 -0
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket/errors.py +0 -0
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket.egg-info/dependency_links.txt +0 -0
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket.egg-info/requires.txt +0 -0
- {mailsocket-0.1.3 → mailsocket-0.2.0}/mailsocket.egg-info/top_level.txt +0 -0
- {mailsocket-0.1.3 → mailsocket-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailsocket
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Topic :: Communications :: Email
|
|
20
|
+
Classifier: Typing :: Typed
|
|
20
21
|
Requires-Python: >=3.9
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
License-File: LICENSE
|
|
@@ -72,9 +73,9 @@ client = Client(api_key, base_url="https://dash.mailsocket.app/api/v1")
|
|
|
72
73
|
### The moat
|
|
73
74
|
|
|
74
75
|
```python
|
|
75
|
-
result = client.wait_for_otp("inbox_abc123", timeout=60, min_confidence=0.0, since=
|
|
76
|
-
result = client.wait_for_link("inbox_abc123", timeout=60, since=
|
|
77
|
-
result = client.wait("inbox_abc123", timeout=60, min_confidence=0.0, since=
|
|
76
|
+
result = client.wait_for_otp("inbox_abc123", timeout=60, min_confidence=0.0, since=None)
|
|
77
|
+
result = client.wait_for_link("inbox_abc123", timeout=60, since=None)
|
|
78
|
+
result = client.wait("inbox_abc123", timeout=60, min_confidence=0.0, since=None) # otp OR link
|
|
78
79
|
```
|
|
79
80
|
|
|
80
81
|
All three return a `WaitResult` and take keyword-only arguments after
|
|
@@ -83,9 +84,16 @@ call with no extra arguments for the common case.
|
|
|
83
84
|
|
|
84
85
|
| Method | Keyword args (all optional, keyword-only) | Returns |
|
|
85
86
|
| --- | --- | --- |
|
|
86
|
-
| `wait_for_otp(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=
|
|
87
|
-
| `wait_for_link(inbox_id, ...)` | `timeout=60`, `since=
|
|
88
|
-
| `wait(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=
|
|
87
|
+
| `wait_for_otp(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=None` | `WaitResult` |
|
|
88
|
+
| `wait_for_link(inbox_id, ...)` | `timeout=60`, `since=None` | `WaitResult` |
|
|
89
|
+
| `wait(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=None` | `WaitResult` (otp OR link) |
|
|
90
|
+
|
|
91
|
+
`since=None` (the default) omits the param, so the server uses its own
|
|
92
|
+
default: the request start time — a code that arrives during this call is
|
|
93
|
+
caught, and a stale code already in a reused inbox is not. Pass `since=0` to
|
|
94
|
+
restore the old "any message already in the inbox" behaviour (useful right
|
|
95
|
+
after `create_inbox`, where there's nothing stale to match), or a message id
|
|
96
|
+
/ timestamp to continue after a specific point.
|
|
89
97
|
|
|
90
98
|
`WaitResult` exposes `.otp`, `.confidence`, `.magic_link` (and `.link` as an
|
|
91
99
|
alias), plus the full `.message` dict. `str(result)` is the OTP (or the link).
|
|
@@ -123,4 +131,9 @@ invalid id never reaches the network.
|
|
|
123
131
|
|
|
124
132
|
## Changelog
|
|
125
133
|
|
|
134
|
+
- **0.2.0** — BEHAVIOUR CHANGE: `wait_for_otp`/`wait_for_link`/`wait` now
|
|
135
|
+
default `since=None`, which omits the param so the server uses the
|
|
136
|
+
request start time, instead of the old `since=0` ("any message already
|
|
137
|
+
in the inbox"). The old default silently returned a STALE OTP/link from a
|
|
138
|
+
reused inbox. Pass `since=0` explicitly to keep the old behaviour.
|
|
126
139
|
- **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.
|
|
@@ -46,9 +46,9 @@ client = Client(api_key, base_url="https://dash.mailsocket.app/api/v1")
|
|
|
46
46
|
### The moat
|
|
47
47
|
|
|
48
48
|
```python
|
|
49
|
-
result = client.wait_for_otp("inbox_abc123", timeout=60, min_confidence=0.0, since=
|
|
50
|
-
result = client.wait_for_link("inbox_abc123", timeout=60, since=
|
|
51
|
-
result = client.wait("inbox_abc123", timeout=60, min_confidence=0.0, since=
|
|
49
|
+
result = client.wait_for_otp("inbox_abc123", timeout=60, min_confidence=0.0, since=None)
|
|
50
|
+
result = client.wait_for_link("inbox_abc123", timeout=60, since=None)
|
|
51
|
+
result = client.wait("inbox_abc123", timeout=60, min_confidence=0.0, since=None) # otp OR link
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
All three return a `WaitResult` and take keyword-only arguments after
|
|
@@ -57,9 +57,16 @@ call with no extra arguments for the common case.
|
|
|
57
57
|
|
|
58
58
|
| Method | Keyword args (all optional, keyword-only) | Returns |
|
|
59
59
|
| --- | --- | --- |
|
|
60
|
-
| `wait_for_otp(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=
|
|
61
|
-
| `wait_for_link(inbox_id, ...)` | `timeout=60`, `since=
|
|
62
|
-
| `wait(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=
|
|
60
|
+
| `wait_for_otp(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=None` | `WaitResult` |
|
|
61
|
+
| `wait_for_link(inbox_id, ...)` | `timeout=60`, `since=None` | `WaitResult` |
|
|
62
|
+
| `wait(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=None` | `WaitResult` (otp OR link) |
|
|
63
|
+
|
|
64
|
+
`since=None` (the default) omits the param, so the server uses its own
|
|
65
|
+
default: the request start time — a code that arrives during this call is
|
|
66
|
+
caught, and a stale code already in a reused inbox is not. Pass `since=0` to
|
|
67
|
+
restore the old "any message already in the inbox" behaviour (useful right
|
|
68
|
+
after `create_inbox`, where there's nothing stale to match), or a message id
|
|
69
|
+
/ timestamp to continue after a specific point.
|
|
63
70
|
|
|
64
71
|
`WaitResult` exposes `.otp`, `.confidence`, `.magic_link` (and `.link` as an
|
|
65
72
|
alias), plus the full `.message` dict. `str(result)` is the OTP (or the link).
|
|
@@ -97,4 +104,9 @@ invalid id never reaches the network.
|
|
|
97
104
|
|
|
98
105
|
## Changelog
|
|
99
106
|
|
|
107
|
+
- **0.2.0** — BEHAVIOUR CHANGE: `wait_for_otp`/`wait_for_link`/`wait` now
|
|
108
|
+
default `since=None`, which omits the param so the server uses the
|
|
109
|
+
request start time, instead of the old `since=0` ("any message already
|
|
110
|
+
in the inbox"). The old default silently returned a STALE OTP/link from a
|
|
111
|
+
reused inbox. Pass `since=0` explicitly to keep the old behaviour.
|
|
100
112
|
- **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.
|
|
@@ -10,6 +10,7 @@ import time as _time
|
|
|
10
10
|
import urllib.error
|
|
11
11
|
import urllib.parse
|
|
12
12
|
import urllib.request
|
|
13
|
+
from dataclasses import dataclass, field
|
|
13
14
|
|
|
14
15
|
from .errors import AuthError, MailsocketError, NotFound, RateLimited, WaitTimeout
|
|
15
16
|
|
|
@@ -79,14 +80,30 @@ class Page:
|
|
|
79
80
|
return f"Page(data={self.data!r}, pagination={self.pagination!r})"
|
|
80
81
|
|
|
81
82
|
|
|
83
|
+
@dataclass(frozen=True, eq=False)
|
|
82
84
|
class WaitResult:
|
|
83
|
-
"""The outcome of a ``wait_for_otp`` / ``wait_for_link`` / ``wait`` call.
|
|
85
|
+
"""The outcome of a ``wait_for_otp`` / ``wait_for_link`` / ``wait`` call.
|
|
86
|
+
|
|
87
|
+
#31 (trust-audit-OUT.md): a frozen dataclass, backward compatible with
|
|
88
|
+
the plain-class shape it replaces — same four public attributes
|
|
89
|
+
(``.message``, ``.otp``, ``.confidence``, ``.magic_link``), the derived
|
|
90
|
+
``.link`` property, and the same ``repr()``/``str()``. Instances are now
|
|
91
|
+
immutable (``frozen=True``); ``eq=False`` keeps equality/hashing
|
|
92
|
+
identity-based exactly as the old plain class, since ``message`` is a
|
|
93
|
+
dict and value-equality would make ``hash()`` raise ``TypeError``.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
message: dict
|
|
97
|
+
otp: str | None = field(init=False)
|
|
98
|
+
confidence: float | None = field(init=False)
|
|
99
|
+
magic_link: str | None = field(init=False)
|
|
84
100
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
self
|
|
89
|
-
self
|
|
101
|
+
def __post_init__(self):
|
|
102
|
+
# frozen=True disallows plain attribute assignment even inside
|
|
103
|
+
# __post_init__, hence object.__setattr__.
|
|
104
|
+
object.__setattr__(self, "otp", self.message.get("otp"))
|
|
105
|
+
object.__setattr__(self, "confidence", self.message.get("otp_confidence"))
|
|
106
|
+
object.__setattr__(self, "magic_link", self.message.get("magic_link"))
|
|
90
107
|
|
|
91
108
|
@property
|
|
92
109
|
def link(self):
|
|
@@ -192,9 +209,16 @@ class Client:
|
|
|
192
209
|
*,
|
|
193
210
|
timeout: float = 60.0,
|
|
194
211
|
min_confidence: float = 0.0,
|
|
195
|
-
since=
|
|
212
|
+
since=None,
|
|
196
213
|
) -> WaitResult:
|
|
197
|
-
"""Wait for an OTP. Returns a :class:`WaitResult` with ``.otp`` etc.
|
|
214
|
+
"""Wait for an OTP. Returns a :class:`WaitResult` with ``.otp`` etc.
|
|
215
|
+
|
|
216
|
+
``since`` defaults to ``None``, which omits the param entirely so the
|
|
217
|
+
server's own default applies (the request start time) — a caller
|
|
218
|
+
only sees messages that arrive during this call, never a stale OTP
|
|
219
|
+
already sitting in a reused inbox. Pass ``since=0`` to restore the
|
|
220
|
+
old behaviour: match any message already in the inbox.
|
|
221
|
+
"""
|
|
198
222
|
return self._wait(
|
|
199
223
|
inbox_id,
|
|
200
224
|
require="otp",
|
|
@@ -203,8 +227,12 @@ class Client:
|
|
|
203
227
|
since=since,
|
|
204
228
|
)
|
|
205
229
|
|
|
206
|
-
def wait_for_link(self, inbox_id: str, *, timeout: float = 60.0, since=
|
|
207
|
-
"""Wait for a magic link. Returns a :class:`WaitResult` with ``.link``.
|
|
230
|
+
def wait_for_link(self, inbox_id: str, *, timeout: float = 60.0, since=None) -> WaitResult:
|
|
231
|
+
"""Wait for a magic link. Returns a :class:`WaitResult` with ``.link``.
|
|
232
|
+
|
|
233
|
+
``since`` defaults to ``None`` (omitted -> server uses the request
|
|
234
|
+
start time). Pass ``since=0`` to include messages already in the inbox.
|
|
235
|
+
"""
|
|
208
236
|
return self._wait(
|
|
209
237
|
inbox_id, require="link", timeout=timeout, min_confidence=None, since=since
|
|
210
238
|
)
|
|
@@ -215,9 +243,13 @@ class Client:
|
|
|
215
243
|
*,
|
|
216
244
|
timeout: float = 60.0,
|
|
217
245
|
min_confidence: float = 0.0,
|
|
218
|
-
since=
|
|
246
|
+
since=None,
|
|
219
247
|
) -> WaitResult:
|
|
220
|
-
"""Wait for either an OTP or a magic link.
|
|
248
|
+
"""Wait for either an OTP or a magic link.
|
|
249
|
+
|
|
250
|
+
``since`` defaults to ``None`` (omitted -> server uses the request
|
|
251
|
+
start time). Pass ``since=0`` to include messages already in the inbox.
|
|
252
|
+
"""
|
|
221
253
|
return self._wait(
|
|
222
254
|
inbox_id,
|
|
223
255
|
require="any",
|
|
@@ -288,7 +320,7 @@ class Client:
|
|
|
288
320
|
url += "?" + urllib.parse.urlencode(params, doseq=True)
|
|
289
321
|
headers = {
|
|
290
322
|
"Accept": "application/json",
|
|
291
|
-
"User-Agent": "mailsocket-python/0.
|
|
323
|
+
"User-Agent": "mailsocket-python/0.2.0",
|
|
292
324
|
}
|
|
293
325
|
headers.update(self._extra_headers)
|
|
294
326
|
# Set last so nothing in extra_headers can ever replace it.
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailsocket
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Topic :: Communications :: Email
|
|
20
|
+
Classifier: Typing :: Typed
|
|
20
21
|
Requires-Python: >=3.9
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
License-File: LICENSE
|
|
@@ -72,9 +73,9 @@ client = Client(api_key, base_url="https://dash.mailsocket.app/api/v1")
|
|
|
72
73
|
### The moat
|
|
73
74
|
|
|
74
75
|
```python
|
|
75
|
-
result = client.wait_for_otp("inbox_abc123", timeout=60, min_confidence=0.0, since=
|
|
76
|
-
result = client.wait_for_link("inbox_abc123", timeout=60, since=
|
|
77
|
-
result = client.wait("inbox_abc123", timeout=60, min_confidence=0.0, since=
|
|
76
|
+
result = client.wait_for_otp("inbox_abc123", timeout=60, min_confidence=0.0, since=None)
|
|
77
|
+
result = client.wait_for_link("inbox_abc123", timeout=60, since=None)
|
|
78
|
+
result = client.wait("inbox_abc123", timeout=60, min_confidence=0.0, since=None) # otp OR link
|
|
78
79
|
```
|
|
79
80
|
|
|
80
81
|
All three return a `WaitResult` and take keyword-only arguments after
|
|
@@ -83,9 +84,16 @@ call with no extra arguments for the common case.
|
|
|
83
84
|
|
|
84
85
|
| Method | Keyword args (all optional, keyword-only) | Returns |
|
|
85
86
|
| --- | --- | --- |
|
|
86
|
-
| `wait_for_otp(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=
|
|
87
|
-
| `wait_for_link(inbox_id, ...)` | `timeout=60`, `since=
|
|
88
|
-
| `wait(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=
|
|
87
|
+
| `wait_for_otp(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=None` | `WaitResult` |
|
|
88
|
+
| `wait_for_link(inbox_id, ...)` | `timeout=60`, `since=None` | `WaitResult` |
|
|
89
|
+
| `wait(inbox_id, ...)` | `timeout=60`, `min_confidence=0.0`, `since=None` | `WaitResult` (otp OR link) |
|
|
90
|
+
|
|
91
|
+
`since=None` (the default) omits the param, so the server uses its own
|
|
92
|
+
default: the request start time — a code that arrives during this call is
|
|
93
|
+
caught, and a stale code already in a reused inbox is not. Pass `since=0` to
|
|
94
|
+
restore the old "any message already in the inbox" behaviour (useful right
|
|
95
|
+
after `create_inbox`, where there's nothing stale to match), or a message id
|
|
96
|
+
/ timestamp to continue after a specific point.
|
|
89
97
|
|
|
90
98
|
`WaitResult` exposes `.otp`, `.confidence`, `.magic_link` (and `.link` as an
|
|
91
99
|
alias), plus the full `.message` dict. `str(result)` is the OTP (or the link).
|
|
@@ -123,4 +131,9 @@ invalid id never reaches the network.
|
|
|
123
131
|
|
|
124
132
|
## Changelog
|
|
125
133
|
|
|
134
|
+
- **0.2.0** — BEHAVIOUR CHANGE: `wait_for_otp`/`wait_for_link`/`wait` now
|
|
135
|
+
default `since=None`, which omits the param so the server uses the
|
|
136
|
+
request start time, instead of the old `since=0` ("any message already
|
|
137
|
+
in the inbox"). The old default silently returned a STALE OTP/link from a
|
|
138
|
+
reused inbox. Pass `since=0` explicitly to keep the old behaviour.
|
|
126
139
|
- **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.
|
|
7
|
+
version = "0.2.0"
|
|
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"
|
|
@@ -21,6 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.11",
|
|
22
22
|
"Programming Language :: Python :: 3.12",
|
|
23
23
|
"Topic :: Communications :: Email",
|
|
24
|
+
"Typing :: Typed",
|
|
24
25
|
]
|
|
25
26
|
dependencies = []
|
|
26
27
|
|
|
@@ -36,6 +37,9 @@ Issues = "https://github.com/mailsocket/mailsocket-sdk/issues"
|
|
|
36
37
|
[tool.setuptools]
|
|
37
38
|
packages = ["mailsocket"]
|
|
38
39
|
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
mailsocket = ["py.typed"]
|
|
42
|
+
|
|
39
43
|
[tool.pytest.ini_options]
|
|
40
44
|
testpaths = ["tests"]
|
|
41
45
|
pythonpath = ["."]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Offline unit tests for the mailsocket Python SDK (mocked transport, no network)."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import urllib.parse
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
6
7
|
|
|
@@ -109,6 +110,70 @@ def test_wait_for_otp_returns_on_first_200(monkeypatch):
|
|
|
109
110
|
assert len(transport.calls) == 1
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
def test_wait_result_equality_and_hash_are_identity_based():
|
|
114
|
+
"""R2 FIX 3 (GPT review): frozen=True makes WaitResult immutable, but
|
|
115
|
+
equality/hash must stay identity-based exactly as the old plain class —
|
|
116
|
+
value-equality would make hash() raise TypeError because ``message``
|
|
117
|
+
is an unhashable dict."""
|
|
118
|
+
a = WaitResult(dict(OTP_MESSAGE))
|
|
119
|
+
b = WaitResult(dict(OTP_MESSAGE))
|
|
120
|
+
|
|
121
|
+
assert a == a
|
|
122
|
+
assert a != b # same field values, but distinct instances
|
|
123
|
+
assert hash(a) == hash(a)
|
|
124
|
+
assert hash(a) != hash(b) or a is b # identity-based hash, no collision assumed
|
|
125
|
+
assert {a, b} == {a, b} # hashable: usable in a set/dict key
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_wait_for_otp_default_since_is_omitted(monkeypatch):
|
|
129
|
+
"""since=None default must OMIT the `since` param so the server uses the
|
|
130
|
+
request start time, not match a stale message already in a reused inbox.
|
|
131
|
+
Revert-proof: restoring since=0 as the default fails this test."""
|
|
132
|
+
client, transport = make_client([(200, {}, {"data": OTP_MESSAGE})])
|
|
133
|
+
_monkeypatch_transport(monkeypatch, client, transport)
|
|
134
|
+
|
|
135
|
+
client.wait_for_otp("inbox_abc", timeout=60)
|
|
136
|
+
|
|
137
|
+
_, url = transport.calls[0]
|
|
138
|
+
query = urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)
|
|
139
|
+
assert "since" not in query
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_wait_for_link_default_since_is_omitted(monkeypatch):
|
|
143
|
+
client, transport = make_client([(200, {}, {"data": LINK_MESSAGE})])
|
|
144
|
+
_monkeypatch_transport(monkeypatch, client, transport)
|
|
145
|
+
|
|
146
|
+
client.wait_for_link("inbox_abc", timeout=60)
|
|
147
|
+
|
|
148
|
+
_, url = transport.calls[0]
|
|
149
|
+
query = urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)
|
|
150
|
+
assert "since" not in query
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_wait_default_since_is_omitted(monkeypatch):
|
|
154
|
+
client, transport = make_client([(200, {}, {"data": OTP_MESSAGE})])
|
|
155
|
+
_monkeypatch_transport(monkeypatch, client, transport)
|
|
156
|
+
|
|
157
|
+
client.wait("inbox_abc", timeout=60)
|
|
158
|
+
|
|
159
|
+
_, url = transport.calls[0]
|
|
160
|
+
query = urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)
|
|
161
|
+
assert "since" not in query
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_wait_for_otp_explicit_since_zero_still_sent(monkeypatch):
|
|
165
|
+
"""Explicit since=0 ("include messages already in the inbox") must still
|
|
166
|
+
be forwarded as a literal 0 on the wire."""
|
|
167
|
+
client, transport = make_client([(200, {}, {"data": OTP_MESSAGE})])
|
|
168
|
+
_monkeypatch_transport(monkeypatch, client, transport)
|
|
169
|
+
|
|
170
|
+
client.wait_for_otp("inbox_abc", timeout=60, since=0)
|
|
171
|
+
|
|
172
|
+
_, url = transport.calls[0]
|
|
173
|
+
query = urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)
|
|
174
|
+
assert query["since"] == ["0"]
|
|
175
|
+
|
|
176
|
+
|
|
112
177
|
def test_wait_keeps_polling_through_204s_then_returns(monkeypatch):
|
|
113
178
|
responses = [
|
|
114
179
|
(204, {}, None),
|
|
@@ -510,13 +575,13 @@ def test_version_exported():
|
|
|
510
575
|
|
|
511
576
|
|
|
512
577
|
def test_metadata_consistency_across_files():
|
|
513
|
-
"""
|
|
578
|
+
"""Version must match everywhere: pyproject, __version__, user agent."""
|
|
514
579
|
import re
|
|
515
580
|
import tomllib
|
|
516
581
|
from pathlib import Path
|
|
517
582
|
|
|
518
583
|
root = Path(__file__).resolve().parents[1]
|
|
519
|
-
expected = "0.
|
|
584
|
+
expected = "0.2.0"
|
|
520
585
|
|
|
521
586
|
pyproject = tomllib.loads((root / "pyproject.toml").read_text())
|
|
522
587
|
assert pyproject["project"]["version"] == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|